1.執行緒池簡介
使用執行緒池,一般會使用JDK提供的幾種封裝型別,即:newFixedThreadPool
、newSingleThreadExecutor
、newCachedThreadPool
等,這些執行緒池的定義在Executors
類中,來看看相關的原始碼:
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
threadFactory);
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
這些方法內部都使用了ThreadPoolExecutor
的構造方法,區別只是傳入的引數不同。ThreadPoolExecutor
有四個過載的構造方法,最終呼叫的是由7個引數的構造器,其原始碼如下:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
//引數校驗
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
引數解釋:
corePoolSize
:核心池大小,預設情況下,執行緒池啟動之後,並不會立即建立執行緒,而是要等到任務到來之後,才建立執行緒去執行任務(除非設定了allowCoreThreadTimeOut
引數,該引數會線上程池啟動之後立馬建立核心池數量的執行緒)。隨著任務的不斷增加,現有執行緒無法滿足要求,就會不斷的建立新執行緒,直到執行緒數達到corePoolSize
的值,後續新來的任務會放入阻塞佇列;maximumPoolSize
: 最大池大小,當任務太多,阻塞佇列滿了之後,如果執行緒數量還沒有超過該引數的值,就會繼續建立新執行緒,直到執行緒數達到該引數規定的值,後續再來的任務會使用拒絕策略進行處理;keepAliveTime
: 如果執行緒數超過corePoolSize
的值,那麼多餘的執行緒在空閒keepAliveTime
時間後會被銷燬;unit
:keepAliveTime
引數的單位;workQueue
: 阻塞佇列;threadFactory
: 執行緒工廠,建立執行緒時需要使用到該工廠;handler
: 拒絕策略。
2.核心欄位
ThreadPoolExecutor
的核心欄位如下:
//ctl低29位表示執行緒的數量,高3位表示執行緒池狀態,因此當前執行緒池允許的最大執行緒數量是2^29-1
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//固定值29
private static final int COUNT_BITS = Integer.SIZE - 3;
//執行緒最大容量
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// runState is stored in the high-order bits
//執行緒池的執行時狀態,負數表示正在執行,正數表示終止情況
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
3.執行緒池狀態
執行緒池的狀態有5種,狀態之間的轉換關係如下圖:
初始情況下,執行緒池建立完畢後會處於RUNNING
狀態,可以正常的接受新任務;當呼叫shutdown()
時,執行緒池變成SHUTDOWN
狀態,此時無法接受新任務,但是會繼續執行阻塞佇列中的任務;當呼叫shutdownNow()
時,執行緒由RUNNING
狀態變成STOP
狀態,此時不能接受新任務,並且會中斷正在執行的任務;當執行緒池中的執行緒數減少為0時,就會轉成TIDYING
狀態;在TIDYING
狀態會自動呼叫terminated()
使執行緒池轉為TERMINATED
狀態。
shutdown()
shutdown()
方法的邏輯分別由5個不同的方法來實現,這裡將這些方法整理在一起,如下:
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//檢查security manager是否允許呼叫方執行此方法
checkShutdownAccess();
//將執行緒池狀態更新為SHUTDOWN
advanceRunState(SHUTDOWN);
//中斷空閒執行緒
interruptIdleWorkers();
//這是一個空實現,允許子類進行重寫
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
tryTerminate();
}
private void advanceRunState(int targetState) {
for (;;) {
int c = ctl.get();
//如果執行緒池已經處在targetState及之後的狀態則直接結束迴圈,否則使用CAS操作將執行緒池狀態更新為targetState
if (runStateAtLeast(c, targetState) ||
ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))))
break;
}
}
private void interruptIdleWorkers() {
interruptIdleWorkers(false);
}
//onlyOne表示是否只終止一個空閒執行緒
private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
//加可重入鎖
mainLock.lock();
try {
for (Worker w : workers) {
Thread t = w.thread;
//如果執行緒沒有被中斷,則嘗試獲取鎖,獲取成功後將執行緒中斷
if (!t.isInterrupted() && w.tryLock()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
} finally {
//釋放鎖
w.unlock();
}
}
if (onlyOne)
break;
}
} finally {
mainLock.unlock();
}
}
final void tryTerminate() {
//自旋
for (;;) {
int c = ctl.get();
//執行緒池還在執行,或者已經是TIDYING或TERMINATED狀態,或者阻塞佇列不為空,這幾種情況不再繼續執行
if (isRunning(c) ||
runStateAtLeast(c, TIDYING) ||
(runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
return;
//執行緒數不為0時,終止一個空閒執行緒
if (workerCountOf(c) != 0) { // Eligible to terminate
interruptIdleWorkers(ONLY_ONE);
return;
}
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//將執行緒池設定為DIDYING狀態
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
//設定成功後,執行terminated()方法
try {
//這也是一個空實現,子類可以根據需要進行重寫
terminated();
} finally {
//將執行緒池設定為TERMINATED狀態
ctl.set(ctlOf(TERMINATED, 0));
termination.signalAll();
}
return;
}
} finally {
mainLock.unlock();
}
// else retry on failed CAS
}
}
shutdownNow()
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//檢查security manager是否允許呼叫方執行此方法
checkShutdownAccess();
//將執行緒池狀態更新為STOP
advanceRunState(STOP);
//與shutdown的區別是,這裡會中斷所有執行緒,而不僅僅是空閒執行緒
interruptWorkers();
//將任務從workQueue中移除,轉移到一個ArrayList中,此操作後,workQueue為空,已有的任務無法繼承執行
tasks = drainQueue();
} finally {
mainLock.unlock();
}
tryTerminate();
return tasks;
}
//中斷所有執行緒
private void interruptWorkers() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers)
w.interruptIfStarted();
} finally {
mainLock.unlock();
}
}
4.執行任務
執行緒池通過execute()
方法執行任務,其原始碼如下:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
//如果當前活躍執行緒小於核心池大小,就嘗試建立新的執行緒
if (workerCountOf(c) < corePoolSize) {
//如果成功建立新執行緒並且啟動成功,直接返回
if (addWorker(command, true))
return;
c = ctl.get();
}
//執行緒池處於執行狀態,並且成功將任務加入阻塞佇列時,會執行下面的程式碼
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
//如果重複檢查時,執行緒池已經不是執行狀態,則將剛新增的任務從阻塞佇列中移除,並執行拒絕策略
if (! isRunning(recheck) && remove(command))
reject(command);
//如果活躍執行緒為0,則建立一個非核心執行緒,並將firstTask設定為null
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//如果新增非核心執行緒失敗,則執行拒絕策略
else if (!addWorker(command, false))
reject(command);
}
//獲取活躍的執行緒數
private static int workerCountOf(int c) { return c & CAPACITY; }
//獲取執行緒池執行狀態
private static int runStateOf(int c) { return c & ~CAPACITY; }
來看看addWorker()
方法的實現:
//core表示要建立的是否是核心執行緒,true表示建立核心執行緒,false表示建立非核心執行緒
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
//獲取執行緒池狀態
int rs = runStateOf(c);
// Check if queue empty only if necessary.
//rs >= SHUTDOWN,表示執行緒池已終止
//rs>=SHUTDOWN,說明已經呼叫了shutdown()或者shutdownNow()方法,在此條件滿足的情況下,第二項條件等同於
//rs!=SHUTDOWN || firstTask != null || workQueue.isEmpty(),滿足這三個條件的任何一個都不會再新增新任務
//rs!=SHUTDOWN,說明是STOP、TIDYING、TERMINATE這三種
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
//執行到這裡說明:
//① rs<SHUTDOWN,即執行緒池是執行狀態
//② rs=SHUTDOWN,farstTask=null, 並且阻塞佇列不為空
for (;;) {
int wc = workerCountOf(c);
//有三種情況會返回false:1)執行緒數達到最大值;2)當前建立核心執行緒,但是執行緒數已經達到核心池大小;
//3)當前建立非核心執行緒,並且執行緒數達到最大池大小
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
//如果使用CAS操作成功將ctl的值加1,則跳出最外層迴圈
if (compareAndIncrementWorkerCount(c))
break retry;
//走到這裡說明無法使用CAS更新ctl的值,說明此時發生了多執行緒競爭,需要重新檢視執行緒池的狀態
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
//加重入鎖
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
//workers是個HashSet型別,只在重入鎖程式碼中被訪問
workers.add(w);
//更新當前活躍執行緒的最大值
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
//啟動執行緒,呼叫Worker類的run()方法
t.start();
workerStarted = true;
}
}
} finally {
//成功建立新執行緒時,才會設定workerStarted=true,這裡處理沒有建立新執行緒的情況
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
addWorker()
方法中用到了Worker
類,這是ThreadPoolExecutor
的內部類,對執行緒進行了包裝,執行緒池建立或者啟動的執行緒,實際都是Worker
型別的例項,其原始碼如下(省略了無關程式碼):
private final class Worker extends AbstractQueuedSynchronizer implements Runnable
{
/** Thread this worker is running in. Null if factory fails. */
final Thread thread;
/** Initial task to run. Possibly null. */
Runnable firstTask;
/** Per-thread task counter */
volatile long completedTasks;
Worker(Runnable firstTask) {
setState(-1);
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
/** Delegates main run loop to outer runWorker */
public void run() {
runWorker(this);
}
當啟動Worker
執行緒時,會呼叫runWorker()
方法,每一個啟動的執行緒都會在該方法的while
迴圈中不斷獲取任務去執行,該方法原始碼如下:
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
//如果能夠成功拿到任務,則執行下面的程式碼塊,如果getTask()方法返回null,當前執行緒就會執行退出邏輯
while (task != null || (task = getTask()) != null) {
//如果能將state欄位設定為1,表示成功拿到鎖,就接著向下執行,否則執行緒會加入等待佇列,不再繼續執行
//注意這裡是在成功拿到新任務之後才會加鎖,結合shutdown()方法的邏輯
w.lock();
// If pool is stopping, ensure thread is interrupted;
// if not, ensure thread is not interrupted. This
// requires a recheck in second case to deal with
// shutdownNow race while clearing interrupt
//如果執行緒池正在關閉,需要中斷當前執行緒
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
//前置鉤子
beforeExecute(wt, task);
Throwable thrown = null;
try {
//執行任務
task.run();
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
//後置鉤子
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
//釋放鎖
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
beforeExecute()
和afterExecute()
是protected
型別,並且預設是空實現,很明顯是留給子類去實現鉤子邏輯。上面的程式碼使用getTask()
從阻塞佇列中取任務,其實現如下:
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
//執行緒池正在關閉,或者阻塞佇列空了,就減少執行緒數,並返回null
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// Are workers subject to culling?
//在設定了allowCoreThreadTimeOut引數後,超過給定的時間,會將空閒的核心執行緒清理掉
//或者執行緒數量超過了核心池數量,會在一定時間後清理掉多餘的執行緒
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
//1)執行緒數量超過最大池數量,或者超時; 2)執行緒數大於1,或者阻塞佇列為空; 這兩個條件都成立時,就將ctl值減1
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
//如果設定了超時狀態,則使用poll方法取任務,超過keepAliveTime還沒有任務到來就返回true
//否則使用take取任務,在阻塞佇列為空時會一直等待
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
//執行緒有可能在等待新任務的到來而阻塞,但是在等待的過程中呼叫shutdownNow()關閉執行緒時,執行緒會丟擲中斷異常,在這裡被捕獲
timedOut = false;
}
}
}
現在來整理一下runWorker()
方法的思路:每一個新建立的執行緒都會在runWorker()
方法裡通過while
迴圈不斷地從阻塞佇列中獲取任務,取到任務之後就執行任務的run()
方法,取不到任務就會一直阻塞,或者等待一定的時間之後,空閒執行緒超時需要回收,就會執行processWorkerExit()
方法。
5.執行緒池是如何關閉的
shutdown()
在介紹shutdown()
方法時有一個疑問,該方法只會中斷空閒執行緒,但是非空閒的執行緒不會被中斷,即使該執行緒被阻塞,因此該方法有可能無法關閉那些一直處在等待狀態的非空閒執行緒,這一點在使用時需要注意。在runWorker()
方法中,while
迴圈會在成功拿到任務後才會加鎖,因此那些由於阻塞佇列為空拿不到任務而阻塞的執行緒也會被shutdown()
方法中斷
while (task != null || (task = getTask()) != null) {
//如果能將state欄位設定為1,表示成功拿到鎖,就接著向下執行,否則執行緒會加入等待佇列,不再繼續執行
//注意這裡是在成功拿到新任務之後才會加鎖,結合shutdown()方法的邏輯
w.lock();
//忽略其他程式碼
}
shutdownNow()
shutdownNow()
會中斷所有的存活執行緒,不論這些執行緒是否空閒,因此可能會導致任務在執行的過程中丟擲異常,這點需要注意。
不論是呼叫哪個方法來關閉執行緒池,最終執行緒的退出是要根據getTask()
方法來決定。當getTask()
方法返回null,即當前阻塞佇列已經沒有任務時,執行緒會退出,並且在getTask()
方法的自旋程式碼會首先檢查執行緒池的狀態,如下:
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
在呼叫shutdownNow()
方法關閉執行緒池後,rs >= STOP
邏輯成立,直接返回null,而shutdown()
方法會繼續執行阻塞佇列中的任務,直到workQueue.isEmpty()
條件為真,getTask()
返回null
導致執行緒一個個結束,不論是哪種情況,最終執行緒池中的執行緒數量都會變成0。