面試官:說說停止線程池的執(zhí)行流程?
對于我們使用的線程池 ThreadPoolExecutor 來說,停止線程池的方法有以下兩個:
- shutdown():優(yōu)雅的關(guān)閉線程池,即不再接受新任務(wù),但會等待已提交任務(wù)(包括正在執(zhí)行的任務(wù)和在隊列中等待的任務(wù))執(zhí)行完畢。等待****所有任務(wù)都執(zhí)行完畢后,線程池才會進(jìn)入終止?fàn)顟B(tài)。
- shutdownNow():嘗試停止所有正在執(zhí)行的任務(wù),并返回等待執(zhí)行的任務(wù)列表。正在執(zhí)行的任務(wù)可能會被中斷,適用于需要立即停止線程池,但不關(guān)心正在執(zhí)行的任務(wù)是否立即完成的情況下。
1.代碼演示
下面通過代碼案例,咱們來了解一下 shutdown() 和 shutdownNow() 方法的具體使用。
1.1 shutdown() 方法執(zhí)行
我們將線程池核心和最大線程數(shù)都設(shè)置為 2,任務(wù)隊列可以存儲 10 個任務(wù),一次性添加了 5 個任務(wù),每個任務(wù)執(zhí)行 2s 以上,添加完任務(wù)之后執(zhí)行停止方法,并在 1s 之后嘗試添加另一個新任務(wù),如下代碼所示:
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadPoolExecutorShutdownTest {
public static void main(String[] args) {
// 創(chuàng)建線程
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2,
2,
1000,
TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<Runnable>(10),
new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println("執(zhí)行拒絕策略");
}
});
// 添加任務(wù)
for (int i = 0; i < 5; i++) {
executor.submit(() -> {
String tName = Thread.currentThread().getName();
System.out.println(tName + ":開始執(zhí)行任務(wù)!");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(tName + ":結(jié)束執(zhí)行任務(wù)!");
});
}
// 停止線程
executor.shutdown();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 添加新任務(wù)
executor.submit(() -> System.out.println("最后一個新任務(wù)"));
}
}
以上程序的執(zhí)行結(jié)果如下:
圖片
從以上結(jié)果可以看出,執(zhí)行 shutdown() 方法后,程序會等待線程池中的所有任務(wù)全部執(zhí)行完在關(guān)閉,再次期間線程池會拒絕加入新任務(wù),并調(diào)用線程池的拒絕策略。
1.2 shutdownNow()方法執(zhí)行
如果將 shutdown() 方法換成 shutdownNow() 方法后,以上程序的執(zhí)行結(jié)果如下:
圖片
也就是說,調(diào)用 shutdownNow() 之后,正在執(zhí)行的任務(wù)會被立即停止,且任務(wù)隊列中未執(zhí)行的任務(wù)也會被清除,調(diào)用 shutdownNow() 方法后新加入的任務(wù)會被拒絕,并執(zhí)行線程池的拒絕策略。
2.shutdown()執(zhí)行流程
shutdown() 方法執(zhí)行源碼如下:
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(SHUTDOWN);
interruptIdleWorkers();
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
tryTerminate();
}
該源碼執(zhí)行流程如下:
- 加鎖:在多線程環(huán)境下,關(guān)閉操作涉及到修改關(guān)鍵狀態(tài)和執(zhí)行一些可能影響多個線程的操作。使用鎖可以確保這些操作的原子性和一致性,避免多個線程同時進(jìn)行關(guān)閉操作導(dǎo)致數(shù)據(jù)不一致或出現(xiàn)意外情況
- 檢查關(guān)閉權(quán)限:在關(guān)閉之前進(jìn)行狀態(tài)檢查可以確保關(guān)閉操作是合法的,避免在不適當(dāng)?shù)臅r候進(jìn)行關(guān)閉。推進(jìn)狀態(tài)可以讓其他代碼部分能夠根據(jù)當(dāng)前執(zhí)行器的狀態(tài)做出正確的反應(yīng)。
- 將狀態(tài)設(shè)置為 SHUTDOWN:阻止新任務(wù)提交但完成現(xiàn)有任務(wù)。
- 中斷空閑線程。
- 調(diào)用 onShutdown 方法(鉤子方法):可能用于在關(guān)閉時執(zhí)行一些特定的清理或自定義操作,比如釋放資源等。
- 釋放鎖。
- 嘗試終止線程池:如果所有任務(wù)已完成的情況下,會真正的終止線程池。
shutdown() 方法的執(zhí)行流程如下圖所示:
圖片