線程
“線程池”,顧名思義就是一個線程緩存,線程是稀缺資源,如果被無限制的創建,不僅會消耗系統資源,還會降低系統的穩定性,因此Java中提供線程池對線程進行統一分配、調優和監控。
線程池介紹
在web開發中,服務器需要接受並處理請求,所以會爲一個請求來分配一個線程來進行處理。如果每次請求都新創建一個線程的話實現起來非常簡便,但是存在一個問題:
如果併發的請求數量非常多,但每個線程執行的時間很短,這樣就會頻繁的創建和銷燬線程,如此一來會大大降低系統的效率。可能出現服務器在爲每個請求創建新線程和銷燬線程上花費的時間和消耗的系統資源要比處理實際的用戶請求的時間和資源更多。
那麼有沒有一種辦法使執行完一個任務,並不被銷燬,而是可以繼續執行其他的任務呢?
這就是線程池的目的了。線程池爲線程生命週期的開銷和資源不足問題提供瞭解決方案。通過對多個任務重用線程,線程創建的開銷被分攤到了多個任務上。
什麼時候使用線程池?
-
單個任務處理時間比較短
-
需要處理的任務數量很大
線程池優勢
-
重用存在的線程,減少線程創建,消亡的開銷,提高性能
-
提高響應速度。當任務到達時,任務可以不需要的等到線程創建就能立即執行。
-
提高線程的可管理性。線程是稀缺資源,如果無限制的創建,不僅會消耗系統資源,還會降低系統的穩定性,使用線程池可以進行統一的分配,調優和監控。
線程的實現方式
Runnable,Thread,Callable
// 實現Runnable接口的類將被Thread執行,表示一個基本的任務
public interface Runnable {
// run方法就是它所有的內容,就是實際執行的任務
public abstract void run();
}
//Callable同樣是任務,與Runnable接口的區別在於它接收泛型,同時它執行任務後帶有返回內容
public interface Callable<V> {
// 相對於run方法的帶有返回值的call方法
V call() throws Exception;
}
Executor框架
Executor接口是線程池框架中最基礎的部分,定義了一個用於執行Runnable的execute方法。
下圖爲它的繼承與實現
從圖中可以看出Executor下有一個重要子接口ExecutorService,其中定義了線程池的具體行爲
1,execute(Runnable command):履行Ruannable類型的任務,
2,submit(task):可用來提交Callable或Runnable任務,並返回代表此任務的Future對象
3,shutdown():在完成已提交的任務後封閉辦事,不再接管新任務,
4,shutdownNow():停止所有正在履行的任務並封閉辦事。
5,isTerminated():測試是否所有任務都履行完畢了。
6,isShutdown():測試是否該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;
ctl 是對線程池的運行狀態和線程池中有效線程的數量進行控制的一個字段, 它包含兩部分的信息: 線程池的運行狀態 (runState) 和線程池內有效線程的數量 (workerCount),這裏可以看到,使用了Integer類型來保存,高3位保存runState,低29位保存workerCount。COUNT_BITS 就是29,CAPACITY就是1左移29位減1(29個1),這個常量表示workerCount的上限值,大約是5億。
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:獲取運行狀態和活動線程數的值。
線程池存在5種狀態
RUNNING = -1 << COUNT_BITS; //高3位爲111
SHUTDOWN = 0 << COUNT_BITS; //高3位爲000
STOP = 1 << COUNT_BITS; //高3位爲001
TIDYING = 2 << COUNT_BITS; //高3位爲010
TERMINATED = 3 << COUNT_BITS; //高3位爲011
1、RUNNING
(1) 狀態說明:線程池處在RUNNING狀態時,能夠接收新任務,以及對已添加的任務進行處理。
(02) 狀態切換:線程池的初始化狀態是RUNNING。換句話說,線程池被一旦被創建,就處於RUNNING狀態,並且線程池中的任務數爲0!
2、 SHUTDOWN
(1) 狀態說明:線程池處在SHUTDOWN狀態時,不接收新任務,但能處理已添加的任務。
(2) 狀態切換:調用線程池的shutdown()接口時,線程池由RUNNING -> SHUTDOWN。
3、STOP
(1) 狀態說明:線程池處在STOP狀態時,不接收新任務,不處理已添加的任務,並且會中斷正在處理的任務。
(2) 狀態切換:調用線程池的shutdownNow()接口時,線程池由(RUNNING or SHUTDOWN ) -> STOP。
4、TIDYING
(1) 狀態說明:當所有的任務已終止,ctl記錄的”任務數量”爲0,線程池會變爲TIDYING狀態。當線程池變爲TIDYING狀態時,會執行鉤子函數terminated()。terminated()在ThreadPoolExecutor類中是空的,若用戶想在線程池變爲TIDYING時,進行相應的處理;可以通過重載terminated()函數來實現。
(2) 狀態切換:當線程池在SHUTDOWN狀態下,阻塞隊列爲空並且線程池中執行的任務也爲空時,就會由 SHUTDOWN -> TIDYING。 當線程池在STOP狀態下,線程池中執行的任務爲空時,就會由STOP -> TIDYING。
5、 TERMINATED
(1) 狀態說明:線程池徹底終止,就變成TERMINATED狀態。
(2) 狀態切換:線程池處在TIDYING狀態時,執行完terminated()之後,就會由 TIDYING -> TERMINATED。
進入TERMINATED的條件如下:
-
線程池不是RUNNING狀態;
-
線程池狀態不是TIDYING狀態或TERMINATED狀態;
-
如果線程池狀態是SHUTDOWN並且workerQueue爲空;
-
workerCount爲0;
-
設置TIDYING狀態成功。
線程池的具體實現
ThreadPoolExecutor 默認線程池
ScheduledThreadPoolExecutor 定時線程池
ThreadPoolExecutor線程池的創建
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
任務提交
1、public void execute() //提交任務無返回值
2、public Future<?> submit() //任務執行完成後有返回值
參數解釋
corePoolSize
線程池中的核心線程數,當提交一個任務時,線程池創建一個新線程執行任務,直到當前線程數等於corePoolSize;如果當前線程數爲corePoolSize,繼續提交的任務被保存到阻塞隊列中,等待被執行;如果執行了線程池的prestartAllCoreThreads()方法,線程池會提前創建並啓動所有核心線程。
maximumPoolSize
線程池中允許的最大線程數。如果當前阻塞隊列滿了,且繼續提交任務,則創建新的線程執行任務,前提是當前線程數小於maximumPoolSize;
keepAliveTime
線程池維護線程所允許的空閒時間。當線程池中的線程數量大於corePoolSize的時候,如果這時沒有新的任務提交,核心線程外的線程不會立即銷燬,而是會等待,直到等待的時間超過了keepAliveTime;
unit
keepAliveTime的單位;
workQueue
用來保存等待被執行的任務的阻塞隊列,且任務必須實現Runable接口,在JDK中提供瞭如下阻塞隊列:
-
1、ArrayBlockingQueue:基於數組結構的有界阻塞隊列,按FIFO排序任務;
-
2、LinkedBlockingQuene:基於鏈表結構的阻塞隊列,按FIFO排序任務,吞吐量通常要高於ArrayBlockingQuene;
-
3、SynchronousQuene:一個不存儲元素的阻塞隊列,每個插入操作必須等到另一個線程調用移除操作,否則插入操作一直處於阻塞狀態,吞吐量通常要高於LinkedBlockingQuene;
-
4、priorityBlockingQuene:具有優先級的無界阻塞隊列;
threadFactory
它是ThreadFactory類型的變量,用來創建新線程。默認使用Executors.defaultThreadFactory() 來創建線程。使用默認的ThreadFactory來創建線程時,會使新創建的線程具有相同的NORM_PRIORITY優先級並且是非守護線程,同時也設置了線程的名稱。
handler
線程池的飽和策略,當阻塞隊列滿了,且沒有空閒的工作線程,如果繼續提交任務,必須採取一種策略處理該任務,線程池提供了4種策略:
-
1、AbortPolicy:直接拋出異常,默認策略;
-
2、CallerRunsPolicy:用調用者所在的線程來執行任務;
-
3、DiscardOldestPolicy:丟棄阻塞隊列中靠最前的任務,並執行當前任務;
-
4、DiscardPolicy:直接丟棄任務;
上面的4種策略都是ThreadPoolExecutor的內部類。
當然也可以根據應用場景實現RejectedExecutionHandler接口,自定義飽和策略,如記錄日誌或持久化存儲不能處理的任務。
線程池監控
public long getTaskCount() //線程池已執行與未執行的任務總數
public long getCompletedTaskCount() //已完成的任務數
public int getPoolSize() //線程池當前的線程數
public int getActiveCount() //線程池中正在執行任務的線程數量
線程池原理
源碼分析
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方法執行流程如下:
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 {
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;
}
Worker類
線程池中的每一個線程被封裝成一個Worker對象,ThreadPool維護的其實就是一組Worker對象,請參見JDK源碼。
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) {
//cas修改state,不可重入
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
tryAcquire方法是根據state是否是0來判斷的,所以,setState(-1);將state設置爲-1是爲了禁止在執行任務前對線程進行中斷。
正因爲如此,在runWorker方法中會先調用Worker對象的unlock方法將state設置爲0。
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 ((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方法中會對該變量的值進行判斷。
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方法。
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方法,整個線程結束,如圖所示:
總結
-
分析了線程的創建,任務的提交,狀態的轉換以及線程池的關閉;
-
這裏通過execute方法來展開線程池的工作流程,execute方法通過corePoolSize,maximumPoolSize以及阻塞隊列的大小來判斷決定傳入的任務應該被立即執行,還是應該添加到阻塞隊列中,還是應該拒絕任務。
-
介紹了線程池關閉時的過程,也分析了shutdown方法與getTask方法存在競態條件;
-
在獲取任務時,要通過線程池的狀態來判斷應該結束工作線程還是阻塞線程等待新的任務,也解釋了爲什麼關閉線程池時要中斷工作線程以及爲什麼每一個worker都需要lock。
最後
歡迎大家一起交流,喜歡文章記得點個贊喲,感謝支持!歡迎大家關注我的公種浩【程序員追風】,整理了2019年多家公司java面試題資料100多頁pdf文檔,文章都會在裏面更新,整理的資料也會放在裏面。