前言
這篇文章主要是針對線程池章的一些內容進行補充和中喲啊源代碼的解析,關於線程池的一些基礎知識我在Java面試07——併發知識點彙總及源碼解析已經做了一些總結,所以一些基礎的知識點南國在這裏有的就不在講述了。深入剖析系列屬於對併發知識的一些地方的詳細剖析,所以這裏更加側重於一到兩個知識點的詳細剖析。這篇博客的總結參考:
話不多說,乾貨送上~
1. 線程池的整體結構圖
Executor框架是一個根據一組執行策略調用,調度,執行和控制的異步任務的框架,目的是提供一種將”任務提交”與”任務如何運行”分離開來的機制。
J.U.C中有三個Executor接口:
- Executor:一個運行新任務的簡單接口;
- ExecutorService:擴展了Executor接口。添加了一些用來管理執行器生命週期和任務生命週期的方法;
- ScheduledExecutorService:擴展了ExecutorService。支持Future和定期執行任務。
1.1 Executor接口
public interface Executor {
void execute(Runnable command);
}
Executor接口只有一個execute方法,用來替代通常創建或啓動線程的方法。例如,使用Thread來創建並啓動線程的代碼如下:
Thread t = new Thread();
t.start();
使用Executor來啓動線程執行任務的代碼如下:
Thread t = new Thread();
executor.execute(t);
對於不同的Executor實現,execute()方法可能是創建一個新線程並立即啓動,也有可能是使用已有的工作線程來運行傳入的任務,也可能是根據設置線程池的容量或者阻塞隊列的容量來決定是否要將傳入的線程放入阻塞隊列中或者拒絕接收傳入的線程。
1.2 ExecutorService接口
ExecutorService接口繼承自Executor接口,提供了管理終止的方法,以及可爲跟蹤一個或多個異步任務執行狀況而生成 Future 的方法。增加了shutDown(),shutDownNow(),invokeAll(),invokeAny()和submit()等方法。如果需要支持即時關閉,也就是shutDownNow()方法,則任務需要正確處理中斷。
1.3 ScheduledExecutorService接口
ScheduledExecutorService擴展ExecutorService接口並增加了schedule方法。調用schedule方法可以在指定的延時後執行一個Runnable或者Callable任務。ScheduledExecutorService接口還定義了按照指定時間間隔定期執行任務的scheduleAtFixedRate()方法和scheduleWithFixedDelay()方法。
2. 深入剖析 ThreadPoolExecutor
ThreadPoolExecutor繼承自AbstractExecutorService,也是實現了ExecutorService接口。
下面是它的幾個重要的字段
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
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;
ctl是對線程池的運行狀態和線程池中有效線程的數量進行控制的一個字段, 它包含兩部分的信息: 線程池的運行狀態 (runState) 和線程池內有效線程的數量 (workerCount),這裏可以看到,使用了Integer類型來保存,高3位保存runState,低29位保存workerCount。COUNT_BITS 就是29,CAPACITY就是1左移29位減1(29個1),這個常量表示workerCount的上限值,大約是5億。
下面再介紹下線程池的運行狀態. 線程池一共有五種狀態(注意南國這裏講的是線程池,之前我們還講述過線程的5種狀態,你還記得嗎?), 分別是:
- RUNNING :能接受新提交的任務,並且也能處理阻塞隊列中的任務;
- SHUTDOWN:關閉狀態,不再接受新提交的任務,但卻可以繼續處理阻塞隊列中已保存的任務。在線程池處於 RUNNING 狀態時,調用 shutdown()方法會使線程池進入到該狀態。(finalize() 方法在執行過程中也會調用shutdown()方法進入該狀態);
- STOP:不能接受新任務,也不處理隊列中的任務,會中斷正在處理任務的線程。在線程池處於 RUNNING 或 SHUTDOWN 狀態時,調用 shutdownNow() 方法會使線程池進入到該狀態;
- TIDYING:如果所有的任務都已終止了,workerCount (有效線程數) 爲0,線程池進入該狀態後會調用 terminated() 方法進入TERMINATED 狀態。
- TERMINATED:在terminated() 方法執行完後進入該狀態,默認terminated()方法中什麼也沒有做。
進入TERMINATED的條件如下:- 線程池不是RUNNING狀態;
- 線程池狀態不是TIDYING狀態或TERMINATED狀態;
- 如果線程池狀態是SHUTDOWN並且workerQueue爲空;
- workerCount爲0;
- 設置TIDYING狀態成功。
下圖爲線程池的狀態轉換過程:
2.1 ctl相關方法
這裏還有幾個對ctl進行計算的方法:
private static int runStateOf(int c) { return c & ~CAPACITY; }
private static int workerCountOf(int c) { return c & CAPACITY; }
private static int ctlOf(int rs, int wc) { return rs | wc; }
- runStateOf:獲取運行狀態;
- workerCountOf:獲取活動線程數;
- ctlOf:獲取運行狀態和活動線程數的值。
2.2 ThreadPoolExecutor構造方法
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:表示核心線程池的大小。當提交一個任務時,如果當前核心線程池的線程個數沒有達到corePoolSize,則會創建新的線程來執行所提交的任務,即使當前核心線程池有空閒的線程。如果當前核心線程池的線程個數已經達到了corePoolSize,則不再重新創建線程。如果調用了prestartCoreThread()或者 prestartAllCoreThreads(),線程池創建的時候所有的核心線程都會被創建並且啓動。
- maximumPoolSize:表示線程池能創建線程的最大個數。如果當阻塞隊列已滿時,並且當前線程池線程個數沒有超過maximumPoolSize的話,就會創建新的線程來執行任務。
- keepAliveTime:空閒線程存活時間。如果當前線程池的線程個數已經超過了corePoolSize,並且線程空閒時間超過了keepAliveTime的話,就會將這些空閒線程銷燬,這樣可以儘可能降低系統資源消耗。
- unit:時間單位。爲keepAliveTime指定時間單位。
- workQueue:阻塞隊列。用於保存任務的阻塞隊列。可以使用ArrayBlockingQueue, LinkedBlockingQueue, SynchronousQueue, PriorityBlockingQueue。
- threadFactory:創建線程的工程類。可以通過指定線程工廠爲每個創建出來的線程設置更有意義的名字,如果出現併發問題,也方便查找問題原因。
- handler:飽和策略。當線程池的阻塞隊列已滿和指定的線程都已經開啓,說明當前線程池已經處於飽和狀態了,那麼就需要採用一種策略來處理這種情況。採用的策略有這幾種:
- AbortPolicy: 直接拒絕所提交的任務,並拋出RejectedExecutionException異常;
- CallerRunsPolicy:只用調用者所在的線程來執行任務;
- DiscardPolicy:不處理直接丟棄掉任務;
- DiscardOldestPolicy:丟棄掉阻塞隊列中存放時間最久的任務,執行當前任務
2.3 execute方法(非常重要!!!)
execute()方法用來提交任務,代碼如下:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*
* clt記錄着runState和workerCount
*/
int c = ctl.get();
/*
* workerCountOf方法取出低29位的值,表示當前活動的線程數;
* 如果當前活動線程數小於corePoolSize,則新建一個線程放入線程池中;
* 並把任務添加到該線程中。
*/
if (workerCountOf(c) < corePoolSize) {
/*
* addWorker中的第二個參數表示限制添加線程的數量是根據corePoolSize來判斷還是maximumPoolSize來判斷;
* 如果爲true,根據corePoolSize來判斷;
* 如果爲false,則根據maximumPoolSize來判斷
*/
if (addWorker(command, true))
return;
/*
* 如果添加失敗,則重新獲取ctl值
*/
c = ctl.get();
}
/*
* 如果當前線程池是運行狀態並且任務添加到隊列成功
*/
if (isRunning(c) && workQueue.offer(command)) {
// 重新獲取ctl值
int recheck = ctl.get();
// 再次判斷線程池的運行狀態,如果不是運行狀態,由於之前已經把command添加到workQueue中了,
// 這時需要移除該command
// 執行過後通過handler使用拒絕策略對該任務進行處理,整個方法返回
if (! isRunning(recheck) && remove(command))
reject(command);
/*
* 獲取線程池中的有效線程數,如果數量是0,則執行addWorker方法
* 這裏傳入的參數表示:
* 1. 第一個參數爲null,表示在線程池中創建一個線程,但不去啓動;
* 2. 第二個參數爲false,將線程池的有限線程數量的上限設置爲maximumPoolSize,添加線程時根據maximumPoolSize來判斷;
* 如果判斷workerCount大於0,則直接返回,在workQueue中新增的command會在將來的某個時刻被執行。
*/
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
/*
* 如果執行到這裏,有兩種情況:
* 1. 線程池已經不是RUNNING狀態;
* 2. 線程池是RUNNING狀態,但workerCount >= corePoolSize並且workQueue已滿。
* 這時,再次調用addWorker方法,但第二個參數傳入爲false,將線程池的有限線程數量的上限設置爲maximumPoolSize;
* 如果失敗則拒絕該任務
*/
else if (!addWorker(command, false))
reject(command);
}
簡單來說,在執行execute()方法時如果狀態一直是RUNNING時,的執行過程如下:
- 如果workerCount < corePoolSize,則創建並啓動一個線程來執行新提交的任務;
- 如果workerCount >= corePoolSize,且線程池內的阻塞隊列未滿,則將任務添加到該阻塞隊列中;
- 如果workerCount >= corePoolSize && workerCount < maximumPoolSize,且線程池內的阻塞隊列已滿,則創建並啓動一個線程來執行新提交的任務;
- 如果workerCount >= maximumPoolSize,並且線程池內的阻塞隊列已滿, 則根據拒絕策略來處理該任務, 默認的處理方式是直接拋異常。
這裏要注意一下addWorker(null, false);,也就是創建一個線程,但並沒有傳入任務,因爲任務已經被添加到workQueue中了,所以worker在執行的時候,會直接從workQueue中獲取任務。所以,在workerCountOf(recheck) == 0時執行addWorker(null, false);也是爲了保證線程池在RUNNING狀態下必須要有一個線程來執行任務。這部分內容,後續小節會具體講解。
execute方法執行流程如下:
還可以使用下面這張圖來理解:
2.4 addWorker方法
addWorker方法的主要工作是在線程池中創建一個新的線程並執行,firstTask參數 用於指定新增的線程執行的第一個任務,core參數爲true表示在新增線程時會判斷當前活動線程數是否少於corePoolSize,false表示新增線程前需要判斷當前活動線程數是否少於maximumPoolSize,代碼如下:
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
// 獲取運行狀態
int rs = runStateOf(c);
/*
* 這個if判斷
* 如果rs >= SHUTDOWN,則表示此時不再接收新任務;
* 接着判斷以下3個條件,只要有1個不滿足,則返回false:
* 1. rs == SHUTDOWN,這時表示關閉狀態,不再接受新提交的任務,但卻可以繼續處理阻塞隊列中已保存的任務
* 2. firsTask爲空
* 3. 阻塞隊列不爲空
*
* 首先考慮rs == SHUTDOWN的情況
* 這種情況下不會接受新提交的任務,所以在firstTask不爲空的時候會返回false;
* 然後,如果firstTask爲空,並且workQueue也爲空,則返回false,
* 因爲隊列中已經沒有任務了,不需要再添加線程了
*/
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
// 獲取線程數
int wc = workerCountOf(c);
// 如果wc超過CAPACITY,也就是ctl的低29位的最大值(二進制是29個1),返回false;
// 這裏的core是addWorker方法的第二個參數,如果爲true表示根據corePoolSize來比較,
// 如果爲false則根據maximumPoolSize來比較。
//
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
// 嘗試增加workerCount,如果成功,則跳出第一個for循環
if (compareAndIncrementWorkerCount(c))
break retry;
// 如果增加workerCount失敗,則重新獲取ctl的值
c = ctl.get(); // Re-read ctl
// 如果當前的運行狀態不等於rs,說明狀態已被改變,返回第一個for循環繼續執行
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 {
// 根據firstTask來創建Worker對象
w = new Worker(firstTask);
// 每一個Worker對象都會創建一個線程
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());
// rs < SHUTDOWN表示是RUNNING狀態;
// 如果rs是RUNNING狀態或者rs是SHUTDOWN狀態並且firstTask爲null,向線程池中添加線程。
// 因爲在SHUTDOWN時不會在添加新的任務,但還是會執行workQueue中的任務
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();
// largestPoolSize記錄着線程池中出現過的最大線程數量
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
// 啓動線程
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
注意一下這裏的t.start()這個語句,啓動時會調用Worker類中的run方法,Worker本身實現了Runnable接口,所以一個Worker類型的對象也是一個線程。
2.5 Worker類
線程池中的每一個線程被封裝成一個Worker對象,ThreadPool維護的其實就是一組Worker對象,看一下Worker的定義:
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
{
/**
* 這個類永遠不會被序列化,但是我們提供了一個serialVersionUID來阻止javac警告。
*/
private static final long serialVersionUID = 6138294804551838833L;
/** 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;
/**
* Creates with given first task and thread from ThreadFactory.
* @param firstTask the first task (null if none)
*/
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
/** Delegates main run loop to outer runWorker */
public void run() {
runWorker(this);
}
// Lock methods
//
// The value 0 represents the unlocked state.
// The value 1 represents the locked state.
protected boolean isHeldExclusively() {
return getState() != 0;
}
protected boolean tryAcquire(int unused) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
protected boolean tryRelease(int unused) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
public void lock() { acquire(1); }
public boolean tryLock() { return tryAcquire(1); }
public void unlock() { release(1); }
public boolean isLocked() { return isHeldExclusively(); }
void interruptIfStarted() {
Thread t;
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
}
Worker類繼承了AQS,並實現了Runnable接口,注意其中的firstTask和thread屬性:firstTask用它來保存傳入的任務;thread是在調用構造方法時通過ThreadFactory來創建的線程,是用來處理任務的線程。
在調用構造方法時,需要把任務傳入,這裏通過getThreadFactory().newThread(this);來新建一個線程,newThread方法傳入的參數是this,因爲Worker本身繼承了Runnable接口,也就是一個線程,所以一個Worker對象在啓動的時候會調用Worker類中的run方法。
Worker繼承了AQS,使用AQS來實現獨佔鎖的功能。爲什麼不使用ReentrantLock來實現呢?可以看到tryAcquire方法,它是不允許重入的,而ReentrantLock是允許重入的:
- lock方法一旦獲取了獨佔鎖,表示當前線程正在執行任務中;
- 如果正在執行任務,則不應該中斷線程;
- 如果該線程現在不是獨佔鎖的狀態,也就是空閒的狀態,說明它沒有在處理任務,這時可以對該線程進行中斷;
- 線程池在執行shutdown方法或tryTerminate方法時會調用interruptIdleWorkers方法來中斷空閒的線程,interruptIdleWorkers方法會使用tryLock方法來判斷線程池中的線程是否是空閒狀態;
- 之所以設置爲不可重入,是因爲我們不希望任務在調用像setCorePoolSize這樣的線程池控制方法時重新獲取鎖。如果使用ReentrantLock,它是可重入的,這樣如果在任務中調用瞭如setCorePoolSize這類線程池控制的方法,會中斷正在運行的線程。
所以,Worker繼承自AQS,用於判斷線程是否空閒以及是否可以被中斷。
此外,在構造方法中執行了setState(-1);,把state變量設置爲-1,爲什麼這麼做呢?是因爲AQS中默認的state是0,如果剛創建了一個Worker對象,還沒有執行任務時,這時就不應該被中斷,看一下tryAquire方法:
protected boolean tryAcquire(int unused) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
tryAcquire方法是根據state是否是0來判斷的,所以,setState(-1);將state設置爲-1是爲了禁止在執行任務前對線程進行中斷。
正因爲如此,在runWorker方法中會先調用Worker對象的unlock方法將state設置爲0.
2.6 runWorker方法
在Worker類中的run方法調用了runWorker方法來執行任務,runWorker方法的代碼如下:
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
// 獲取第一個任務
Runnable task = w.firstTask;
w.firstTask = null;
// 允許中斷
w.unlock(); // allow interrupts
// 是否因爲異常退出循環
boolean completedAbruptly = true;
try {
// 如果task爲空,則通過getTask來獲取任務
while (task != null || (task = getTask()) != null) {
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);
}
}
這裏說明一下第一個if判斷,目的是:
- 如果線程池正在停止,那麼要保證當前線程是中斷狀態;
- 如果不是的話,則要保證當前線程不是中斷狀態;
這裏要考慮在執行該if語句期間可能也執行了shutdownNow方法,shutdownNow方法會把狀態設置爲STOP,回顧一下STOP狀態:
不能接受新任務,也不處理隊列中的任務,會中斷正在處理任務的線程。在線程池處於 RUNNING 或 SHUTDOWN 狀態時,調用 shutdownNow() 方法會使線程池進入到該狀態。
STOP狀態要中斷線程池中的所有線程,而這裏使用Thread.interrupted()來判斷是否中斷是爲了確保在RUNNING或者SHUTDOWN狀態時線程是非中斷狀態的,因爲Thread.interrupted()方法會復位中斷的狀態。
總結一下runWorker方法的執行過程:
- while循環不斷地通過getTask()方法獲取任務;
- getTask()方法從阻塞隊列中取任務;
- 如果線程池正在停止,那麼要保證當前線程是中斷狀態,否則要保證當前線程不是中斷狀態;
- 調用task.run()執行任務;
- 如果task爲null則跳出循環,執行processWorkerExit()方法;
- runWorker方法執行完畢,也代表着Worker中的run方法執行完畢,銷燬線程。
這裏的beforeExecute方法和afterExecute方法在ThreadPoolExecutor類中是空的,留給子類來實現。
completedAbruptly變量來表示在執行任務過程中是否出現了異常,在processWorkerExit方法中會對該變量的值進行判斷。
2.7 getTask方法
getTask方法用來從阻塞隊列中取任務,代碼如下:
private Runnable getTask() {
// timeOut變量的值表示上次從阻塞隊列中取任務時是否超時
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.
/*
* 如果線程池狀態rs >= SHUTDOWN,也就是非RUNNING狀態,再進行以下判斷:
* 1. rs >= STOP,線程池是否正在stop;
* 2. 阻塞隊列是否爲空。
* 如果以上條件滿足,則將workerCount減1並返回null。
* 因爲如果當前線程池狀態的值是SHUTDOWN或以上時,不允許再向阻塞隊列中添加任務。
*/
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// Are workers subject to culling?
// timed變量用於判斷是否需要進行超時控制。
// allowCoreThreadTimeOut默認是false,也就是核心線程不允許進行超時;
// wc > corePoolSize,表示當前線程池中的線程數量大於核心線程數量;
// 對於超過核心線程數量的這些線程,需要進行超時控制
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
/*
* wc > maximumPoolSize的情況是因爲可能在此方法執行階段同時執行了setMaximumPoolSize方法;
* timed && timedOut 如果爲true,表示當前操作需要進行超時控制,並且上次從阻塞隊列中獲取任務發生了超時
* 接下來判斷,如果有效線程數量大於1,或者阻塞隊列是空的,那麼嘗試將workerCount減1;
* 如果減1失敗,則返回重試。
* 如果wc == 1時,也就說明當前線程是線程池中唯一的一個線程了。
*/
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
/*
* 根據timed來判斷,如果爲true,則通過阻塞隊列的poll方法進行超時控制,如果在keepAliveTime時間內沒有獲取到任務,則返回null;
* 否則通過take方法,如果這時隊列爲空,則take方法會阻塞直到隊列不爲空。
*
*/
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
// 如果 r == null,說明已經超時,timedOut設置爲true
timedOut = true;
} catch (InterruptedException retry) {
// 如果獲取任務時當前線程發生了中斷,則設置timedOut爲false並返回循環重試
timedOut = false;
}
}
}
這裏重要的地方是第二個if判斷,目的是控制線程池的有效線程數量。由上文中的分析可以知道,在執行execute方法時,如果當前線程池的線程數量超過了corePoolSize且小於maximumPoolSize,並且workQueue已滿時,則可以增加工作線程,但這時如果超時沒有獲取到任務,也就是timedOut爲true的情況,說明workQueue已經爲空了,也就說明了當前線程池中不需要那麼多線程來執行任務了,可以把多於corePoolSize數量的線程銷燬掉,保持線程數量在corePoolSize即可。
什麼時候會銷燬?當然是runWorker方法執行完之後,也就是Worker中的run方法執行完,由JVM自動回收。
getTask方法返回null時,在runWorker方法中會跳出while循環,然後會執行processWorkerExit方法。
2.8 processWorkerExit方法
private void processWorkerExit(Worker w, boolean completedAbruptly) {
// 如果completedAbruptly值爲true,則說明線程執行時出現了異常,需要將workerCount減1;
// 如果線程執行時沒有出現異常,說明在getTask()方法中已經已經對workerCount進行了減1操作,這裏就不必再減了。
if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
decrementWorkerCount();
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//統計完成的任務數
completedTaskCount += w.completedTasks;
// 從workers中移除,也就表示着從線程池中移除了一個工作線程
workers.remove(w);
} finally {
mainLock.unlock();
}
// 根據線程池狀態進行判斷是否結束線程池
tryTerminate();
int c = ctl.get();
/*
* 當線程池是RUNNING或SHUTDOWN狀態時,如果worker是異常結束,那麼會直接addWorker;
* 如果allowCoreThreadTimeOut=true,並且等待隊列有任務,至少保留一個worker;
* 如果allowCoreThreadTimeOut=false,workerCount不少於corePoolSize。
*/
if (runStateLessThan(c, STOP)) {
if (!completedAbruptly) {
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
if (min == 0 && ! workQueue.isEmpty())
min = 1;
if (workerCountOf(c) >= min)
return; // replacement not needed
}
addWorker(null, false);
}
}
至此,processWorkerExit執行完之後,工作線程被銷燬,以上就是整個工作線程的生命週期,從execute方法開始,Worker使用ThreadFactory創建新的工作線程,runWorker通過getTask獲取任務,然後執行任務,如果getTask返回null,進入processWorkerExit方法,整個線程結束,如圖所示:
2.9 tryTerminate方法
tryTerminate方法根據線程池狀態進行判斷是否結束線程池,代碼如下:
final void tryTerminate() {
for (;;) {
int c = ctl.get();
/*
* 當前線程池的狀態爲以下幾種情況時,直接返回:
* 1. RUNNING,因爲還在運行中,不能停止;
* 2. TIDYING或TERMINATED,因爲線程池中已經沒有正在運行的線程了;
* 3. SHUTDOWN並且等待隊列非空,這時要執行完workQueue中的task;
*/
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 {
// 這裏嘗試設置狀態爲TIDYING,如果設置成功,則調用terminated方法
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
try {
// terminated方法默認什麼都不做,留給子類實現
terminated();
} finally {
// 設置狀態爲TERMINATED
ctl.set(ctlOf(TERMINATED, 0));
termination.signalAll();
}
return;
}
} finally {
mainLock.unlock();
}
// else retry on failed CAS
}
}
interruptIdleWorkers(ONLY_ONE);的作用是因爲在getTask方法中執行workQueue.take()時,如果不執行中斷會一直阻塞。在下面介紹的shutdown方法中,會中斷所有空閒的工作線程,如果在執行shutdown時工作線程沒有空閒,然後又去調用了getTask方法,這時如果workQueue中沒有任務了,調用workQueue.take()時就會一直阻塞。所以每次在工作線程結束時調用tryTerminate方法來嘗試中斷一個空閒工作線程,避免在隊列爲空時取任務一直阻塞的情況。
2.10 shutdown方法
shutdown方法要將線程池切換到SHUTDOWN狀態,並調用interruptIdleWorkers方法請求中斷所有空閒的worker(他還是會將正在執行的任務繼續執行完),最後調用tryTerminate嘗試結束線程池。
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 安全策略判斷
checkShutdownAccess();
// 切換狀態爲SHUTDOWN
advanceRunState(SHUTDOWN);
// 中斷空閒線程
interruptIdleWorkers();
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
// 嘗試結束線程池
tryTerminate();
}
這裏思考一個問題:在runWorker方法中,執行任務時對Worker對象w進行了lock操作,爲什麼要在執行任務的時候對每個工作線程都加鎖呢?
下面仔細分析一下:
- 在getTask方法中,如果這時線程池的狀態是SHUTDOWN並且workQueue爲空,那麼就應該返回null來結束這個工作線程,而使線程池進入SHUTDOWN狀態需要調用shutdown方法;
- shutdown方法會調用interruptIdleWorkers來中斷空閒的線程,interruptIdleWorkers持有mainLock,會遍歷workers來逐個判斷工作線程是否空閒。但getTask方法中沒有mainLock;
- 在getTask中,如果判斷當前線程池狀態是RUNNING,並且阻塞隊列爲空,那麼會調用workQueue.take()進行阻塞;
- 如果在判斷當前線程池狀態是RUNNING後,這時調用了shutdown方法把狀態改爲了SHUTDOWN,這時如果不進行中斷,那麼當前的工作線程在調用了workQueue.take()後會一直阻塞而不會被銷燬,因爲在SHUTDOWN狀態下不允許再有新的任務添加到workQueue中,這樣一來線程池永遠都關閉不了了;
- 由上可知,shutdown方法與getTask方法(從隊列中獲取任務時)存在競態條件;
- 解決這一問題就需要用到線程的中斷,也就是爲什麼要用interruptIdleWorkers方法。在調用workQueue.take()時,如果發現當前線程在執行之前或者執行期間是中斷狀態,則會拋出InterruptedException,解除阻塞的狀態;
- 但是要中斷工作線程,還要判斷工作線程是否是空閒的,如果工作線程正在處理任務,就不應該發生中斷;
- 所以Worker繼承自AQS,在工作線程處理任務時會進行lock,interruptIdleWorkers在進行中斷時會使用tryLock來判斷該工作線程是否正在處理任務,如果tryLock返回true,說明該工作線程當前未執行任務,這時纔可以被中斷。
下面就來分析一下interruptIdleWorkers方法。
2.11 interruptIdleWorkers方法
private void interruptIdleWorkers() {
interruptIdleWorkers(false);
}
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();
}
}
interruptIdleWorkers遍歷workers中所有的工作線程,若線程沒有被中斷tryLock成功,就中斷該線程。
爲什麼需要持有mainLock?因爲workers是HashSet類型的,不能保證線程安全。
2.12 shutdownNow方法
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(STOP);
// 中斷所有工作線程,無論是否空閒
interruptWorkers();
// 取出隊列中沒有被執行的任務
tasks = drainQueue();
} finally {
mainLock.unlock();
}
tryTerminate();
return tasks;
}
shutdownNow方法與shutdown方法類似,不同的地方在於:
- 設置狀態爲STOP;
- 中斷所有工作線程,無論是否是空閒的;
- 取出阻塞隊列中沒有被執行的任務並返回。
shutdownNow方法執行完之後調用tryTerminate方法,該方法在上文已經分析過了,目的就是使線程池的狀態設置爲TERMINATED。
2.13 線程池的監控
通過線程池提供的參數進行監控。線程池裏有一些屬性在監控線程池的時候可以使用
- getTaskCount:線程池已經執行的和未執行的任務總數;
- getCompletedTaskCount:線程池已完成的任務數量,該值小於等於taskCount;
- getLargestPoolSize:線程池曾經創建過的最大線程數量。通過這個數據可以知道線程池是否滿過,也就是達到了maximumPoolSize;
- getPoolSize:線程池當前的線程數量;
- getActiveCount:當前線程池中正在執行任務的線程數量。
通過這些方法,可以對線程池進行監控,在ThreadPoolExecutor類中提供了幾個空方法,如beforeExecute方法,afterExecute方法和terminated方法,可以擴展這些方法在執行前或執行後增加一些新的操作,例如統計線程池的執行任務的時間等,可以繼承自ThreadPoolExecutor來進行擴展。
2.14 ThreadPoolExecutor總結
本文比較詳細的分析了線程池的工作流程,總體來說有如下幾個內容:
- 分析了線程的創建,任務的提交,狀態的轉換以及線程池的關閉;
- 這裏通過execute方法來展開線程池的工作流程,execute方法通過corePoolSize,maximumPoolSize以及阻塞隊列的大小來判斷決定傳入的任務應該被立即執行,還是應該添加到阻塞隊列中,還是應該拒絕任務。
- 介紹了線程池關閉時的過程,也分析了shutdown方法與getTask方法存在競態條件;
- 在獲取任務時,要通過線程池的狀態來判斷應該結束工作線程還是阻塞線程等待新的任務,也解釋了爲什麼關閉線程池時要中斷工作線程以及爲什麼每一個worker都需要lock。
- 在向線程池提交任務時,除了execute方法,還有一個submit方法,submit方法會返回一個Future對象用於獲取返回值。
3. 深入剖析 ScheduledThreadPoolExecutor
查看線程池的整體架構圖,我們可看到ScheduledThreadPoolExecutor繼承了ThreadPoolExecutor,也就是說ScheduledThreadPoolExecutor擁有execute()和submit()提交異步任務的基礎功能。同時,ScheduledThreadPoolExecutor類實現了ScheduledExecutorService,該接口定義了ScheduledThreadPoolExecutor能夠延時執行任務和週期執行任務的功能。
ScheduledThreadPoolExecutor有兩個重要的內部類:DelayedWorkQueue和ScheduledFutureTask。可以看出DelayedWorkQueue實現了BlockingQueue接口,也就是一個阻塞隊列,ScheduledFutureTask則是繼承了FutureTask類,也表示該類用於返回異步任務的結果。這兩個關鍵類,下面會具體詳細來看。
3.1 構造方法
ScheduledThreadPoolExecutor有如下幾個構造方法:
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
public ScheduledThreadPoolExecutor(int corePoolSize,
ThreadFactory threadFactory) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue(), threadFactory);
}
public ScheduledThreadPoolExecutor(int corePoolSize,
RejectedExecutionHandler handler) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue(), handler);
}
public ScheduledThreadPoolExecutor(int corePoolSize,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue(), threadFactory, handler);
}
可以看出由於ScheduledThreadPoolExecutor繼承了ThreadPoolExecutor,它的構造方法實際上是調用了ThreadPoolExecutor,其實理解ThreadPoolExecutor構造方法的幾個參數的意義後,理解這就很容易了。可以看出,ScheduledThreadPoolExecutor的核心線程池的線程個數爲指定的corePoolSize,當核心線程池的線程個數達到corePoolSize後,就會將任務提交給有界阻塞隊列DelayedWorkQueue,對DelayedWorkQueue在下面進行詳細介紹,線程池允許最大的線程個數爲Integer.MAX_VALUE,也就是說理論上這是一個大小無界的線程池。
3.2 特有方法
ScheduledThreadPoolExecutor實現了ScheduledExecutorService接口,該接口定義了可延時執行異步任務和可週期執行異步任務的特有功能,相應的方法分別爲:
//達到給定的延時時間後,執行任務。這裏傳入的是實現Runnable接口的任務,
//因此通過ScheduledFuture.get()獲取結果爲null
public ScheduledFuture<?> schedule(Runnable command,
long delay, TimeUnit unit);
//達到給定的延時時間後,執行任務。這裏傳入的是實現Callable接口的任務,
//因此,返回的是任務的最終計算結果
public <V> ScheduledFuture<V> schedule(Callable<V> callable,
long delay, TimeUnit unit);
//是以上一個任務開始的時間計時,period時間過去後,
//檢測上一個任務是否執行完畢,如果上一個任務執行完畢,
//則當前任務立即執行,如果上一個任務沒有執行完畢,則需要等上一個任務執行完畢後立即執行
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit);
//當達到延時時間initialDelay後,任務開始執行。上一個任務執行結束後到下一次
//任務執行,中間延時時間間隔爲delay。以這種方式,週期性執行任務。
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
long initialDelay,
long delay,
TimeUnit unit);
3.3 可週期性執行的任務—ScheduledFutureTask
ScheduledThreadPoolExecutor最大的特色是能夠週期性執行異步任務,當調用schedule,scheduleAtFixedRate和scheduleWithFixedDelay方法時,實際上是將提交的任務轉換成的ScheduledFutureTask類,從源碼就可以看出。以schedule方法爲例:
public ScheduledFuture<?> schedule(Runnable command,
long delay,
TimeUnit unit) {
if (command == null || unit == null)
throw new NullPointerException();
RunnableScheduledFuture<?> t = decorateTask(command,
new ScheduledFutureTask<Void>(command, null,
triggerTime(delay, unit)));
delayedExecute(t);
return t;
}
可以看出,通過decorateTask會將傳入的Runnable轉換成ScheduledFutureTask類。線程池最大作用是將任務和線程進行解耦,線程主要是任務的執行者,而任務也就是現在所說的ScheduledFutureTask。緊接着,會想到任何線程執行任務,總會調用run()方法。爲了保證ScheduledThreadPoolExecutor能夠延時執行任務以及能夠週期性執行任務,ScheduledFutureTask重寫了run方法:
public void run() {
boolean periodic = isPeriodic();
if (!canRunInCurrentRunState(periodic))
cancel(false);
else if (!periodic)
//如果不是週期性執行任務,則直接調用run方法
ScheduledFutureTask.super.run();
//如果是週期性執行任務的話,需要重設下一次執行任務的時間
else if (ScheduledFutureTask.super.runAndReset()) {
setNextRunTime();
reExecutePeriodic(outerTask);
}
}
從源碼可以很明顯的看出,在重寫的run方法中會先if (!periodic)判斷當前任務是否是週期性任務,如果不是的話就直接調用run()方法;否則的話執行setNextRunTime()方法重設下一次任務執行的時間,並通過reExecutePeriodic(outerTask)方法將下一次待執行的任務放置到DelayedWorkQueue中。
因此,可以得出結論:ScheduledFutureTask最主要的功能是根據當前任務是否具有週期性,對異步任務進行進一步封裝。如果不是週期性任務(調用schedule方法)則直接通過run()執行,若是週期性任務,則需要在每一次執行完後,重設下一次執行的時間,然後將下一次任務繼續放入到阻塞隊列中。
3.4 DelayedWorkQueue
在ScheduledThreadPoolExecutor中還有另外的一個重要的類就是DelayedWorkQueue。爲了實現其ScheduledThreadPoolExecutor能夠延時執行異步任務以及能夠週期執行任務,DelayedWorkQueue進行相應的封裝。DelayedWorkQueue是一個基於堆的數據結構,類似於DelayQueue和PriorityQueue。在執行定時任務的時候,每個任務的執行時間都不同,所以DelayedWorkQueue的工作就是按照執行時間的升序來排列,執行時間距離當前時間越近的任務在隊列的前面。
爲什麼要使用DelayedWorkQueue呢?
定時任務執行時需要取出最近要執行的任務,所以任務在隊列中每次出隊時一定要是當前隊列中執行時間最靠前的,所以自然要使用優先級隊列。
DelayedWorkQueue是一個優先級隊列,它可以保證每次出隊的任務都是當前隊列中執行時間最靠前的,由於它是基於堆結構的隊列,堆結構在執行插入和刪除操作時的最壞時間複雜度是 O(logN)。
DelayedWorkQueue的數據結構
//初始大小
private static final int INITIAL_CAPACITY = 16;
//DelayedWorkQueue是由一個大小爲16的數組組成,數組元素爲實現RunnableScheduleFuture接口的類
//實際上爲ScheduledFutureTask
private RunnableScheduledFuture<?>[] queue =
new RunnableScheduledFuture<?>[INITIAL_CAPACITY];
private final ReentrantLock lock = new ReentrantLock();
private int size = 0;
可以看出DelayedWorkQueue底層是採用數組構成的。關於DelayedWorkQueue我們可以得出這樣的結論:DelayedWorkQueue是基於堆的數據結構,按照時間順序將每個任務進行排序,將待執行時間越近的任務放在在隊列的隊頭位置,以便於最先進行執行。
3.5 ScheduledThreadPoolExecutor執行過程
現在我們對ScheduledThreadPoolExecutor的兩個內部類ScheduledFutueTask和DelayedWorkQueue進行了瞭解,實際上這也是線程池工作流程中最重要的兩個關鍵因素:任務以及阻塞隊列。現在我們來看下ScheduledThreadPoolExecutor提交一個任務後,整體的執行過程。以ScheduledThreadPoolExecutor的schedule方法爲例,具體源碼爲:
public ScheduledFuture<?> schedule(Runnable command,
long delay,
TimeUnit unit) {
if (command == null || unit == null)
throw new NullPointerException();
//將提交的任務轉換成ScheduledFutureTask
RunnableScheduledFuture<?> t = decorateTask(command,
new ScheduledFutureTask<Void>(command, null,
triggerTime(delay, unit)));
//延時執行任務ScheduledFutureTask
delayedExecute(t);
return t;
}
方法很容易理解,爲了滿足ScheduledThreadPoolExecutor能夠延時執行任務和能週期執行任務的特性,會先將實現Runnable接口的類轉換成ScheduledFutureTask。然後會調用delayedExecute方法進行執行任務,這個方法也是關鍵方法,來看下源碼:
private void delayedExecute(RunnableScheduledFuture<?> task) {
if (isShutdown())
//如果當前線程池已經關閉,則拒絕任務
reject(task);
else {
//將任務放入阻塞隊列中
super.getQueue().add(task);
if (isShutdown() &&
!canRunInCurrentRunState(task.isPeriodic()) &&
remove(task))
task.cancel(false);
else
//保證至少有一個線程啓動,即使corePoolSize=0
ensurePrestart();
}
}
delayedExecute方法的主要邏輯請看註釋,可以看出該方法的重要邏輯會是在ensurePrestart()方法中,它的源碼爲:
void ensurePrestart() {
int wc = workerCountOf(ctl.get());
if (wc < corePoolSize)
addWorker(null, true);
else if (wc == 0)
addWorker(null, false);
}
可以看出該方法邏輯很簡單,關鍵在於它所調用的addWorker方法,該方法主要功能:新建Worker類,當執行任務時,就會調用被Worker所重寫的run方法,進而會繼續執行runWorker方法。在runWorker方法中會調用getTask方法從阻塞隊列中不斷的去獲取任務進行執行,直到從阻塞隊列中獲取的任務爲null的話,線程結束終止。addWorker方法是ThreadPoolExecutor類中的方法,對ThreadPoolExecutor的源碼分析可以看這篇文章,很詳細。
3.6 總結
-
ScheduledThreadPoolExecutor繼承了ThreadPoolExecutor類,因此,整體上功能一致,線程池主要負責創建線程(Worker類),線程從阻塞隊列中不斷獲取新的異步任務,直到阻塞隊列中已經沒有了異步任務爲止。但是相較於ThreadPoolExecutor來說,ScheduledThreadPoolExecutor具有延時執行任務和可週期性執行任務的特性,ScheduledThreadPoolExecutor重新設計了任務類ScheduleFutureTask,ScheduleFutureTask重寫了run方法使其具有可延時執行和可週期性執行任務的特性。另外,阻塞隊列DelayedWorkQueue是可根據優先級排序的隊列,採用了堆的底層數據結構,使得與當前時間相比,待執行時間越靠近的任務放置隊頭,以便線程能夠獲取到任務進行執行;
-
線程池無論是ThreadPoolExecutor還是ScheduledThreadPoolExecutor,在設計時的三個關鍵要素是:任務,執行者以及任務結果。它們的設計思想也是完全將這三個關鍵要素進行了解耦。
執行者
任務的執行機制,完全交由Worker類,也就是進一步了封裝了Thread。向線程池提交任務,無論爲ThreadPoolExecutor的execute方法和submit方法,還是ScheduledThreadPoolExecutor的schedule方法,都是先將任務移入到阻塞隊列中,然後通過addWork方法新建了Work類,並通過runWorker方法啓動線程,並不斷的從阻塞對列中獲取異步任務執行交給Worker執行,直至阻塞隊列中無法取到任務爲止。
任務
在ThreadPoolExecutor和ScheduledThreadPoolExecutor中任務是指實現了Runnable接口和Callable接口的實現類。ThreadPoolExecutor中會將任務轉換成FutureTask類,而在ScheduledThreadPoolExecutor中爲了實現可延時執行任務和週期性執行任務的特性,任務會被轉換成ScheduledFutureTask類,該類繼承了FutureTask,並重寫了run方法。
任務結果
在ThreadPoolExecutor中提交任務後,獲取任務結果可以通過Future接口的類,在ThreadPoolExecutor中實際上爲FutureTask類,而在ScheduledThreadPoolExecutor中則是ScheduledFutureTask類