概述
在之前的一篇博客里談談ThreadPoolExecutor的實現(xiàn)已經對ThreadPoolExecutor中的線程如何運行進行了簡單的介紹值依,本文將介紹線程池是如何進行結束的,并對上篇文章遺留問題進行解答颇蜡。
功能介紹
java中線程池提供了兩個關閉方法shutdown和shutdownNow辆亏,兩個方法的具體使用如下:
//執(zhí)行該方法,線程處于shutdown狀態(tài)扮叨,線程池不允許再提交任務,但是已提交的任務會繼續(xù)執(zhí)行直到結束
void shutdown();
//執(zhí)行該方法甸鸟,線程會處于stop狀態(tài)兵迅,線程池會試圖停止正在執(zhí)行的任務,并返回沒有執(zhí)行成功的任務列表
List<Runnable> shutdownNow();
源碼分析
我們知道刻恭,ThreadPoolExecutor在會將每個線程封裝為一個Worker對象,該對象會持有任務并且在執(zhí)行完其創(chuàng)建時的第一個任務firstTask后鳍贾,會阻塞的從workQueue中獲取任務。前面我們講到shutdown方法會將已提交的任務執(zhí)行直到結束橡淑,同時會將空閑線程回收(可以先思考下咆爽,如何判斷線程是否空閑?)斗埂。我們進入源碼進行分析:
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
//全局加鎖,保證只會有一個線程內執(zhí)行該方法
mainLock.lock();
try {
//權限檢查男娄,忽略
checkShutdownAccess();
//將線程池狀態(tài)置為SHUTDOWN
advanceRunState(SHUTDOWN);
//中斷空閑線程
interruptIdleWorkers();
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
tryTerminate();
}
private void interruptIdleWorkers() {
interruptIdleWorkers(false);
}
private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//遍歷每個Worker對象
for (Worker w : workers) {
Thread t = w.thread;
//如果沒有被中斷漾稀,并且持有Worker的互斥鎖,說明該woker對象為空閑線程并且沒有被中斷
if (!t.isInterrupted() && w.tryLock()) {
try {
//中斷worker對象持有的線程尸折,因為該線程可能正在阻塞在任務隊列中
t.interrupt();
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
if (onlyOne)
break;
}
} finally {
mainLock.unlock();
}
}
我們可以回到我之前的博客談談ThreadPoolExecutor的實現(xiàn)查看Worker類在執(zhí)行任務之前會首先獲取到自身的互斥鎖缕贡,這樣如果獲取不到Worker的互斥鎖,則說明該worker正在執(zhí)行任務收擦,這就回答了我們上面的問題塞赂,也是為什么Worker類實現(xiàn)AQS的原因(這里并不是為了并發(fā)安全昼蛀,只是為了判斷線程是否正在執(zhí)行任務叼旋,下面會有更深刻的認識)。當我們在主線程中中斷了worker中持有的線程夫植,woker線程中的runWorker方法會執(zhí)行最外圍的processWorkerExit函數銷毀線程油讯,進而完全結束worker的生命周期延欠。
而正在執(zhí)行的線程在執(zhí)行完其任務也會因為獲取不到任務進入processWorkerExit函數,結束線程生命周期兔综。
下面狞玛,我們繼續(xù)看下shutdownNow方法如何實現(xiàn)的,相比于shutdown方法为居,該方法簡直太狠了直接對所有的線程執(zhí)行中斷杀狡,具體代碼如下:
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//權限檢查
checkShutdownAccess();
//將線程池狀態(tài)置為STOP
advanceRunState(STOP);
//中斷所有線程包括
interruptWorkers();
tasks = drainQueue();
} finally {
mainLock.unlock();
}
tryTerminate();
return tasks;
}
//這個函數就比較狠了,無論是否持有鎖膳凝,只要線程沒有被中斷恭陡,就中斷Worker持有的線程
private void interruptWorkers() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers)
w.interruptIfStarted();
} finally {
mainLock.unlock();
}
}
private void interruptWorkers() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//循環(huán)遍歷中斷線程
for (Worker w : workers)
w.interruptIfStarted();
} finally {
mainLock.unlock();
}
}
void interruptIfStarted() {
Thread t;
//無論是否持有互斥鎖,只有線程沒有被中斷就執(zhí)行interrupt
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
使用建議
在日常開發(fā)中著淆,我們一般不會去使用shutdownNow拴疤,這個方法會導致部分任務無法執(zhí)行。我們通常會調用shutdown方法使線程池不接受新的任務苔埋,然后等正在執(zhí)行的任務執(zhí)行完成后再結束蜒犯。下面我給出一個個人覺得比較優(yōu)雅的結束線程池的使用示例:
public class ThreadPoolExecutorTest {
private static Logger logger = LoggerFactory.getLogger(ThreadPoolExecutorTest.class);
private static ExecutorService executorService = Executors.newFixedThreadPool(3);
public static void main(String []args) {
for (int i = 0; i< 5; i++) {
executorService.submit(new SleepRunnable());
}
executorService.shutdown();//執(zhí)行該方法相當于通知線程池結束
try {
//阻塞等待線程池結束
executorService.awaitTermination(1000, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
//出現(xiàn)異常是強制結束
List<Runnable> notExcuteRunnables = executorService.shutdownNow();
logger.info("awaitTermination exception, notExcuteRunnables = {}", notExcuteRunnables, e);
}
}
//測試任務,簡單的sleep 1s
static class SleepRunnable implements Runnable {
@Override
public void run() {
try {
logger.info("sleep in thread = {}", Thread.currentThread().getName());
Thread.sleep(1000);
} catch (InterruptedException e) {
logger.info("exception in sleep", e);
}
}
}
}
原文
袁瓊瓊的技術博客玉工,歡迎指針
http://yuanqiongqiong.cn/2019/07/15/ThreadPoolExecutor%E5%85%B3%E9%97%AD%E7%BA%BF%E7%A8%8B%E6%B1%A0%E8%AF%A6%E8%A7%A3/