想要進階自己的開發(fā)水平,JDK源碼中一些優(yōu)秀的設計必須要經(jīng)常學習,哪怕不學習,應對面試的時候,還是要能夠應對幾招,代表自己對這些東西還是有所了解。
而線程池的源碼,這塊更是面試中經(jīng)常被問到的東西,先試著列幾個問題,看看自己對線程池的掌握程度:
創(chuàng)建線程池的參數(shù)有哪些,分別代表什么意思?
為什么阿里要求不能直接使用Executors工具類創(chuàng)建線程池?
線程池線程的數(shù)量如何配置?
一般線程池提交任務,執(zhí)行任務的過程?
線程池中ctl屬性的作用是什么?
線程池的狀態(tài)有哪些?在什么時候下會出現(xiàn)?
一般線程池中有哪些未實現(xiàn)的空方法,可以用做線程池的擴展?
線程池中每一個具體的worker線程什么時候開始執(zhí)行?執(zhí)行的過程是什么?
核心線程與非核心線程在線程池中是怎么區(qū)分的?
線程池中的那個方法可以提前創(chuàng)建核心線程?
什么情況下worker線程會退出?
核心線程會不會退出?
由于程序異常導致的退出和線程池內部機制導致的退出有什么區(qū)別?
線程池shutdown與shutdownNow有什么區(qū)別?

對上面問題都已經(jīng)了如指掌的大佬,聯(lián)系我,讓我表達對你的膜拜... 以上問題相對來說并不是很難,只要有認真看線程池源碼,都可以找到答案。然后以后有人再問你線程池相關問題時,就可以拿出來說自己對線程池的理解,來聊聊把...
郵箱:aihe.ah@alibaba-inc.com
微信:aihehe5211
常見問題
使用線程池有哪些好處?
首先在開發(fā)的過程中,為什么需要線程池呢?給我們帶來了那些好處
- 提高系統(tǒng)的響應速度
- 如果每次多線程操作都創(chuàng)建一個線程,會浪費時間和消耗系統(tǒng)資源,而線程池可以減少這些操作
- 可以對多個線程進行統(tǒng)一管理,統(tǒng)一調度,提高線程池的可管理性
創(chuàng)建線程池的參數(shù)有哪些?
線程池是怎么創(chuàng)建的呢?一個是使用Executors,另外就是手動創(chuàng)建線程池,要了解其每個參數(shù)的含義。Executors創(chuàng)建線程池的話,要不就是對線程的數(shù)量沒有控制,如CachedThreadPool,要不就是是無界隊列,如FixedThreadPool。對線程池數(shù)量和隊列大小沒有限制的話,容易導致OOM異常。所以我們要自己手動創(chuàng)建線程池:
- corePoolSize:核心線程數(shù)量,默認情況下每提交一個任務就會創(chuàng)建一個核心線程,直到核心線程的數(shù)量等于corePoolSize就不再創(chuàng)建。線程池提供了兩個方法可以提前創(chuàng)建核心線程,
prestartAllCoreThreads()提前創(chuàng)建所有的核心線程,prestartCoreThread,提前創(chuàng)建一個核心線程 - maximumPoolSize:線程池允許創(chuàng)建的最大線程數(shù)。只有當線程池隊列滿的時候才會創(chuàng)建
- keepAliveTime:線程池空閑狀態(tài)可以等待的時間,默認對非核心線程生效,但是設置
allowCoreThreadTimeOut的話對核心線程也生效 - unit: ?;顣r間的單位,創(chuàng)建線程池的時候,
keepAliveTime = unit.toNanos(keepAliveTime) - workQueue: 任務隊列,用于保持或等待執(zhí)行的任務阻塞隊列。BlockingQueue的實現(xiàn)類即可,有無界隊列和有界隊列
- ArrayBlockingQueue: 基于數(shù)組結構的有界隊列,此隊列按FIFO原則對元素進行排序
- LinkedBlockingQueue: 基于鏈表的阻塞隊列,F(xiàn)IFO原則,吞吐量通常高于ArrayBlockingQueue.
- SynchronousQueue: 不存儲元素的阻塞隊列。每個插入必須要等到另一個線程調用移除操作。
- PriorityBlockingQueue: 具有優(yōu)先級的無阻塞隊列
- threadFactory: 用于設置創(chuàng)建線程的工廠。
- handler:拒絕策略,當隊列線程池都滿了,必須采用一種策略來處理還要提交的任務。在實際應用中,我們可以將信息記錄到日志,來分析系統(tǒng)的負載和任務丟失情況JDK中提供了4中策略:
- AbortPolicy: 直接拋出異常
- CallerRunsPolicy: 只用調用者所在的線程來運行任務
- DiscardOldestPolicy: 丟棄隊列中最老的一個人任務,并執(zhí)行當前任務。
- DiscardPolicy: 直接丟棄新進來的任務
線程池提交任務的過程?
可以使用兩個方法執(zhí)行任務:
- execute() 提交不需要返回值的任務,無法判斷是否執(zhí)行成功,具體步驟上面我們有分析
- submit() 提交有返回值的任務,該方法返回一個future的對象,通過future對象可以判斷任務是否執(zhí)行成功。future的get方法會阻塞當前線程直到任務完成。
- submit內部使用RunnableFuture對任務進行封裝
整體分為三個步驟:
- 判斷當前線程數(shù)是否小于corePoolSize,如果小于,則新建核心線程,不管核心線程是否處于空閑狀態(tài)
- 核心線程創(chuàng)建滿之后,后續(xù)的任務添加到workQueue中
- 如果workQueue滿了,則開始創(chuàng)建非核心線程直到線程的總數(shù)為maximumPoolSize
- 當非核心線程數(shù)也滿了,隊列也滿了的時候,執(zhí)行拒絕策略
中間會有一些對當前線程池的檢查操作。

線程池數(shù)量如何配置?
- 任務性質:CPU密集,IO密集,和混合密集
- 任務執(zhí)行時間:長,中,低
- 任務優(yōu)先級:高,中,低
- 任務的依賴性:是否依賴其它資源,如數(shù)據(jù)庫連接
在代碼中可以通過:Runtime.getRuntime().availableProcessors();獲取CPU數(shù)量。線程數(shù)計算公式:
N = CPU數(shù)量
U = 目標CPU使用率, 0 <= U <= 1
W/C = 等待(wait)時間與計算(compute)時間的比率
線程池數(shù)量 = N * U * (1 + W/C)
不過最簡單的線程數(shù)指定方式,不需要公式的話:
- CPU密集型,創(chuàng)建線程數(shù)為
CPU核數(shù) + 1 - IO密集型,線程數(shù)最好為
CPU核數(shù) * n,耗時越久,分配線程數(shù)多一些
線程池的狀態(tài)有哪些?
線程池的狀態(tài)主要通過ctl屬性來控制,通過ctl可以計算出:
- 當前線程池狀態(tài)
- 當前線程的數(shù)量
計算規(guī)則主要是利用了按位操作:
11100000000000000000000000000000 RUNNING
00000000000000000000000000000000 SHUTDOWN
00100000000000000000000000000000 STOP
01000000000000000000000000000000 TYDYING
01100000000000000000000000000000 TERMINATED
11100000000000000000000000000000 ctl初始值
11100000000000000000000000000000 ~CAPACITY
private static int runStateOf(int c) { return c & ~CAPACITY; }
11100000000000000000000000000000 ctl初始值
00011111111111111111111111111111 CAPACITY
private static int workerCountOf(int c) { return c & CAPACITY; }
private static int ctlOf(int rs, int wc) { return rs | wc; }
- RUNNING:運行狀態(tài),接受新任務,持續(xù)處理任務隊列里的任務。
- SHUTDOWN:調用shutdown()方法會進入此狀態(tài),不再接受新任務,但要處理任務隊列里的任務
- STOP:調用shutdownNow()方法,不再接受新任務,不再處理任務隊列里的任務,中斷正在進行中的任務
- TIDYING:表示線程池正在停止運作,中止所有任務,銷毀所有工作線程。
- TERMINATED:表示線程池已停止運作,所有工作線程已被銷毀,所有任務已被清空或執(zhí)行完畢
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;
關于TIDYING和TERMINATED主要有一塊代碼區(qū),可以看出來TIDYING狀態(tài)緊接著就是TERMINATED。
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
try {
// 默認是空方法
terminated();
} finally {
ctl.set(ctlOf(TERMINATED, 0));
termination.signalAll();
}
return;
}
線程池提供的擴展方法有哪些?
默認有三個擴展方法,可以用來做一些線程池運行狀態(tài)統(tǒng)計,監(jiān)控:
protected void beforeExecute(Thread t, Runnable r) { } // task.run方法之前執(zhí)行
protected void afterExecute(Runnable r, Throwable t) { } // task執(zhí)行完之后,不管有沒有異常都會執(zhí)行
protected void terminated() { }
默認線程池也提供了幾個相關的可監(jiān)控屬性:
- taskCount: 線程池需要執(zhí)行的任務數(shù)量
- completedTaskCount: 已經(jīng)完成的任務數(shù)量
- largestPoolSize: 線程池中曾經(jīng)創(chuàng)建的最大的線程數(shù)量
- getPoolSize: 線程池的線程數(shù)量
- getActiveCount: 活動的線程數(shù)
線程池中的Worker線程執(zhí)行的過程?
Worker類實現(xiàn)了Runnable方法,在成功創(chuàng)建Worker線程后就會調用其start方法。
w = new Worker(firstTask);
final Thread t = w.thread; //理解為 w.thread = new Thread(w)
if (workerAdded) {
t.start();
workerStarted = true;
}
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
Worker線程運行時執(zhí)行runWorker方法,里面主要事情:
- 如果構造Worker的時候,指定了firstTask,那么首先執(zhí)行firstTask,否則從隊列中獲取任務
- Worker線程會循環(huán)的getTask(),然后去執(zhí)行任務
- 如果getTask()為空,那么worker線程就會退出
- 在任務執(zhí)行前后,可以自定義擴展beforeExecute與afterExecute方法
- 如果檢測到線程池為STOP狀態(tài),并且線程還沒有被中斷過的話,進行中斷處理
簡單來說就是不斷的從任務隊列中取任務,如果取不到,那么就退出當前的線程,取到任務就執(zhí)行任務。
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
// 代表著Worker是否因為用戶的程序有問題導致的死亡
boolean completedAbruptly = true;
try {
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 (Exception x) {
//... 不同的異常處理
} finally {
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
線程池如何區(qū)分核心線程與非核心線程?
實際上內部在創(chuàng)建線程時,并沒有給線程做標記,因此無法區(qū)分核心線程與非核心線程。可以看出addWorker()方法。
但是為什么可以保持核心線程一直不被銷毀呢?
其內部主要根據(jù)當前線程的數(shù)量來處理。也可以理解為,只要當前的worker線程數(shù)小于配置的corePoolSize,那么這些線程都是核心線程。線程池根據(jù)當前線程池的數(shù)量來判斷要不要退出線程,而不是根據(jù)是否核心線程
核心線程能否被退出?
上面一個問題我們說到了內部其實不區(qū)分核心線程與非核心線程的,只是根據(jù)數(shù)量來判斷是否退出線程,但是線程是如何退出的,又是如何一直處于保活狀態(tài)呢?
如果配置了allowCoreThreadTimeOut,代表核心線程在配置的keepAliveTime時間內沒獲取到任務,會執(zhí)行退出操作。也就是盡管當前線程數(shù)量小于corePoolSize也會執(zhí)行退出線程操作。
workQueue.take()方法會一直阻塞當前的隊列直到有任務的出現(xiàn),因此如果執(zhí)行的是take方法,那么當前的線程就不會退出。想要退出當前的線程,有幾個條件:
- 1 當前的worker數(shù)量大于maximumPoolSize的worker數(shù)量。
- 2 線程池當前處于STOP狀態(tài),即shutdownNow
- 3 線程池處于SHUTDOWN狀態(tài),并且當前的隊列為空
- 4 worker線程等待task超時了,并且當前的worker線程配置為可以被退出。
timed=true- allowCoreThreadTimeOut配置為true
- 線程數(shù)量大于核心線程數(shù)

如何提前創(chuàng)建核心線程數(shù)?
上面提到了,有兩個方法:
-
prestartAllCoreThreads()提前創(chuàng)建所有的核心線程 -
prestartCoreThread,提前創(chuàng)建一個核心線程,如果當前線程數(shù)量大于corePoolSize,則不創(chuàng)建
線程池異常退出與自動退出的區(qū)別?
如果線程是由于程序異常導致的退出,那么completedAbruptly為true,如下代碼會再新建一個Worker線程。
如果線程是系統(tǒng)自動退出,即completedAbruptly為false的話,會根據(jù)配置判斷當前可以允許的最小核心線程數(shù)量
- 配置allowCoreThreadTimeOut為true的話,最小核心線程數(shù)可以為0。
- 默認情況下最小線程數(shù)為corePoolSize
int c = ctl.get();
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);
}
線程池shutdown與shutdownNow有什么區(qū)別?
看代碼主要三個區(qū)別:
- shutdown會把線程池的狀態(tài)改為SHUTDOWN,而shutdownNow把當前線程池狀態(tài)改為STOP
- shutdown只會中斷所有空閑的線程,而shutdownNow會中斷所有的線程。
- shutdown返回方法為空,會將當前任務隊列中的所有任務執(zhí)行完畢;而shutdownNow把任務隊列中的所有任務都取出來返回。

最后
學無止境,還有很多細節(jié),但足以打動面試官,覺得真是一個很用心的候選人呢... 希望這些能幫到你。
還有阿里內推:這邊是阿里集團-淘系技術部的,總裁帶頭發(fā)起項目,新成立部門,業(yè)務急速擴張,目前還有大量的HC,機會多,考慮的聯(lián)系我:
微信:aihehe5211