想要進(jìn)階自己的開發(fā)水平蜕便,JDK源碼中一些優(yōu)秀的設(shè)計必須要經(jīng)常學(xué)習(xí)劫恒,哪怕不學(xué)習(xí),應(yīng)對面試的時候轿腺,還是要能夠應(yīng)對幾招两嘴,代表自己對這些東西還是有所了解。
而線程池的源碼族壳,這塊更是面試中經(jīng)常被問到的東西憔辫,先試著列幾個問題,看看自己對線程池的掌握程度:
創(chuàng)建線程池的參數(shù)有哪些螺垢,分別代表什么意思?
為什么阿里要求不能直接使用Executors工具類創(chuàng)建線程池孽亲?
線程池線程的數(shù)量如何配置栖茉?
一般線程池提交任務(wù),執(zhí)行任務(wù)的過程吼虎?
線程池中ctl屬性的作用是什么?
線程池的狀態(tài)有哪些歹颓?在什么時候下會出現(xiàn)?
一般線程池中有哪些未實現(xiàn)的空方法隔节,可以用做線程池的擴(kuò)展?
線程池中每一個具體的worker線程什么時候開始執(zhí)行?執(zhí)行的過程是什么强胰?
核心線程與非核心線程在線程池中是怎么區(qū)分的距糖?
線程池中的那個方法可以提前創(chuàng)建核心線程恩脂?
什么情況下worker線程會退出典阵?
核心線程會不會退出歹啼?
由于程序異常導(dǎo)致的退出和線程池內(nèi)部機(jī)制導(dǎo)致的退出有什么區(qū)別?
線程池shutdown與shutdownNow有什么區(qū)別屡限?
對上面問題都已經(jīng)了如指掌的大佬炕倘,聯(lián)系我,讓我表達(dá)對你的膜拜... 以上問題相對來說并不是很難罩旋,只要有認(rèn)真看線程池源碼,都可以找到答案瓜饥。然后以后有人再問你線程池相關(guān)問題時东帅,就可以拿出來說自己對線程池的理解,來聊聊把...
郵箱:aihe.ah@alibaba-inc.com
微信:aihehe5211
常見問題
使用線程池有哪些好處帐我?
首先在開發(fā)的過程中愧膀,為什么需要線程池呢?給我們帶來了那些好處
- 提高系統(tǒng)的響應(yīng)速度
- 如果每次多線程操作都創(chuàng)建一個線程檩淋,會浪費時間和消耗系統(tǒng)資源,而線程池可以減少這些操作
- 可以對多個線程進(jìn)行統(tǒng)一管理媚朦,統(tǒng)一調(diào)度氧敢,提高線程池的可管理性
創(chuàng)建線程池的參數(shù)有哪些?
線程池是怎么創(chuàng)建的呢询张?一個是使用Executors,另外就是手動創(chuàng)建線程池份氧,要了解其每個參數(shù)的含義。Executors創(chuàng)建線程池的話恋拷,要不就是對線程的數(shù)量沒有控制厅缺,如CachedThreadPool,要不就是是無界隊列阎抒,如FixedThreadPool消痛。對線程池數(shù)量和隊列大小沒有限制的話都哭,容易導(dǎo)致OOM異常。所以我們要自己手動創(chuàng)建線程池:
- corePoolSize:核心線程數(shù)量纱新,默認(rèn)情況下每提交一個任務(wù)就會創(chuàng)建一個核心線程穆趴,直到核心線程的數(shù)量等于corePoolSize就不再創(chuàng)建未妹。線程池提供了兩個方法可以提前創(chuàng)建核心線程,
prestartAllCoreThreads()
提前創(chuàng)建所有的核心線程络它,prestartCoreThread
化戳,提前創(chuàng)建一個核心線程 - maximumPoolSize:線程池允許創(chuàng)建的最大線程數(shù)。只有當(dāng)線程池隊列滿的時候才會創(chuàng)建
- keepAliveTime:線程池空閑狀態(tài)可以等待的時間扫尖,默認(rèn)對非核心線程生效,但是設(shè)置
allowCoreThreadTimeOut
的話對核心線程也生效 - unit: 被徊溃活時間的單位狰域,創(chuàng)建線程池的時候,
keepAliveTime = unit.toNanos(keepAliveTime)
- workQueue: 任務(wù)隊列兆览,用于保持或等待執(zhí)行的任務(wù)阻塞隊列抬探。BlockingQueue的實現(xiàn)類即可,有無界隊列和有界隊列
- ArrayBlockingQueue: 基于數(shù)組結(jié)構(gòu)的有界隊列线梗,此隊列按FIFO原則對元素進(jìn)行排序
- LinkedBlockingQueue: 基于鏈表的阻塞隊列怠益,F(xiàn)IFO原則,吞吐量通常高于ArrayBlockingQueue.
- SynchronousQueue: 不存儲元素的阻塞隊列亚茬。每個插入必須要等到另一個線程調(diào)用移除操作抢呆。
- PriorityBlockingQueue: 具有優(yōu)先級的無阻塞隊列
- threadFactory: 用于設(shè)置創(chuàng)建線程的工廠抱虐。
- handler:拒絕策略,當(dāng)隊列線程池都滿了恳邀,必須采用一種策略來處理還要提交的任務(wù)谣沸。在實際應(yīng)用中,我們可以將信息記錄到日志鳄抒,來分析系統(tǒng)的負(fù)載和任務(wù)丟失情況JDK中提供了4中策略:
- AbortPolicy: 直接拋出異常
- CallerRunsPolicy: 只用調(diào)用者所在的線程來運行任務(wù)
- DiscardOldestPolicy: 丟棄隊列中最老的一個人任務(wù),并執(zhí)行當(dāng)前任務(wù)瓤鼻。
- DiscardPolicy: 直接丟棄新進(jìn)來的任務(wù)
線程池提交任務(wù)的過程茬祷?
可以使用兩個方法執(zhí)行任務(wù):
- execute() 提交不需要返回值的任務(wù),無法判斷是否執(zhí)行成功祭犯,具體步驟上面我們有分析
- submit() 提交有返回值的任務(wù)沃粗,該方法返回一個future的對象,通過future對象可以判斷任務(wù)是否執(zhí)行成功突雪。future的get方法會阻塞當(dāng)前線程直到任務(wù)完成涡贱。
- submit內(nèi)部使用RunnableFuture對任務(wù)進(jìn)行封裝
整體分為三個步驟:
- 判斷當(dāng)前線程數(shù)是否小于corePoolSize,如果小于督函,則新建核心線程激挪,不管核心線程是否處于空閑狀態(tài)
- 核心線程創(chuàng)建滿之后,后續(xù)的任務(wù)添加到workQueue中
- 如果workQueue滿了,則開始創(chuàng)建非核心線程直到線程的總數(shù)為maximumPoolSize
- 當(dāng)非核心線程數(shù)也滿了锋喜,隊列也滿了的時候豌鸡,執(zhí)行拒絕策略
中間會有一些對當(dāng)前線程池的檢查操作。
線程池數(shù)量如何配置炉奴?
- 任務(wù)性質(zhì):CPU密集蛇更,IO密集赛糟,和混合密集
- 任務(wù)執(zhí)行時間:長璧南,中师逸,低
- 任務(wù)優(yōu)先級:高,中动知,低
- 任務(wù)的依賴性:是否依賴其它資源员辩,如數(shù)據(jù)庫連接
在代碼中可以通過:Runtime.getRuntime().availableProcessors();
獲取CPU數(shù)量。線程數(shù)計算公式:
N = CPU數(shù)量
U = 目標(biāo)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可以計算出:
- 當(dāng)前線程池狀態(tài)
- 當(dāng)前線程的數(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)唱较,接受新任務(wù),持續(xù)處理任務(wù)隊列里的任務(wù)胸遇。
- SHUTDOWN:調(diào)用shutdown()方法會進(jìn)入此狀態(tài)汉形,不再接受新任務(wù)倍阐,但要處理任務(wù)隊列里的任務(wù)
- STOP:調(diào)用shutdownNow()方法收捣,不再接受新任務(wù)庵楷,不再處理任務(wù)隊列里的任務(wù),中斷正在進(jìn)行中的任務(wù)
- TIDYING:表示線程池正在停止運作咐蚯,中止所有任務(wù)弄贿,銷毀所有工作線程。
- TERMINATED:表示線程池已停止運作期奔,所有工作線程已被銷毀危尿,所有任務(wù)已被清空或執(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;
關(guān)于TIDYING和TERMINATED主要有一塊代碼區(qū)谊娇,可以看出來TIDYING狀態(tài)緊接著就是TERMINATED。
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
try {
// 默認(rèn)是空方法
terminated();
} finally {
ctl.set(ctlOf(TERMINATED, 0));
termination.signalAll();
}
return;
}
線程池提供的擴(kuò)展方法有哪些赠堵?
默認(rèn)有三個擴(kuò)展方法法褥,可以用來做一些線程池運行狀態(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() { }
默認(rèn)線程池也提供了幾個相關(guān)的可監(jiān)控屬性:
- taskCount: 線程池需要執(zhí)行的任務(wù)數(shù)量
- completedTaskCount: 已經(jīng)完成的任務(wù)數(shù)量
- largestPoolSize: 線程池中曾經(jīng)創(chuàng)建的最大的線程數(shù)量
- getPoolSize: 線程池的線程數(shù)量
- getActiveCount: 活動的線程數(shù)
線程池中的Worker線程執(zhí)行的過程酱鸭?
Worker類實現(xiàn)了Runnable方法垛吗,在成功創(chuàng)建Worker線程后就會調(diào)用其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方法蔚舀,里面主要事情:
- 如果構(gòu)造Worker的時候,指定了firstTask狼牺,那么首先執(zhí)行firstTask礼患,否則從隊列中獲取任務(wù)
- Worker線程會循環(huán)的getTask(),然后去執(zhí)行任務(wù)
- 如果getTask()為空悄泥,那么worker線程就會退出
- 在任務(wù)執(zhí)行前后肤粱,可以自定義擴(kuò)展beforeExecute與afterExecute方法
- 如果檢測到線程池為STOP狀態(tài),并且線程還沒有被中斷過的話鸥鹉,進(jìn)行中斷處理
簡單來說就是不斷的從任務(wù)隊列中取任務(wù)庶骄,如果取不到瓢姻,那么就退出當(dāng)前的線程,取到任務(wù)就執(zhí)行任務(wù)幻碱。
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
// 代表著Worker是否因為用戶的程序有問題導(dǎo)致的死亡
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ū)分核心線程與非核心線程儡嘶?
實際上內(nèi)部在創(chuàng)建線程時恍风,并沒有給線程做標(biāo)記,因此無法區(qū)分核心線程與非核心線程凯楔。可以看出addWorker()方法
摆屯。
但是為什么可以保持核心線程一直不被銷毀呢糠亩?
其內(nèi)部主要根據(jù)當(dāng)前線程的數(shù)量來處理准验。也可以理解為廷没,只要當(dāng)前的worker線程數(shù)小于配置的corePoolSize颠黎,那么這些線程都是核心線程。線程池根據(jù)當(dāng)前線程池的數(shù)量來判斷要不要退出線程盏缤,而不是根據(jù)是否核心線程
核心線程能否被退出唉铜?
上面一個問題我們說到了內(nèi)部其實不區(qū)分核心線程與非核心線程的,只是根據(jù)數(shù)量來判斷是否退出線程竞惋,但是線程是如何退出的灰嫉,又是如何一直處于保活狀態(tài)呢浑厚?
如果配置了allowCoreThreadTimeOut根盒,代表核心線程在配置的keepAliveTime時間內(nèi)沒獲取到任務(wù),會執(zhí)行退出操作敢艰。也就是盡管當(dāng)前線程數(shù)量小于corePoolSize也會執(zhí)行退出線程操作册赛。
workQueue.take()方法會一直阻塞當(dāng)前的隊列直到有任務(wù)的出現(xiàn),因此如果執(zhí)行的是take方法牡属,那么當(dāng)前的線程就不會退出扼睬。想要退出當(dāng)前的線程,有幾個條件:
- 1 當(dāng)前的worker數(shù)量大于maximumPoolSize的worker數(shù)量证芭。
- 2 線程池當(dāng)前處于STOP狀態(tài)担映,即shutdownNow
- 3 線程池處于SHUTDOWN狀態(tài)蝇完,并且當(dāng)前的隊列為空
- 4 worker線程等待task超時了,并且當(dāng)前的worker線程配置為可以被退出氢架。
timed=true
- allowCoreThreadTimeOut配置為true
- 線程數(shù)量大于核心線程數(shù)
如何提前創(chuàng)建核心線程數(shù)朋魔?
上面提到了警检,有兩個方法:
-
prestartAllCoreThreads()
提前創(chuàng)建所有的核心線程 -
prestartCoreThread
,提前創(chuàng)建一個核心線程拓售,如果當(dāng)前線程數(shù)量大于corePoolSize镶奉,則不創(chuàng)建
線程池異常退出與自動退出的區(qū)別?
如果線程是由于程序異常導(dǎo)致的退出鸽凶,那么completedAbruptly為true移国,如下代碼會再新建一個Worker線程。
如果線程是系統(tǒng)自動退出使碾,即completedAbruptly為false的話祝懂,會根據(jù)配置判斷當(dāng)前可以允許的最小核心線程數(shù)量
- 配置allowCoreThreadTimeOut為true的話,最小核心線程數(shù)可以為0矢门。
- 默認(rèn)情況下最小線程數(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把當(dāng)前線程池狀態(tài)改為STOP
- shutdown只會中斷所有空閑的線程宣旱,而shutdownNow會中斷所有的線程叛薯。
- shutdown返回方法為空,會將當(dāng)前任務(wù)隊列中的所有任務(wù)執(zhí)行完畢组力;而shutdownNow把任務(wù)隊列中的所有任務(wù)都取出來返回抖拴。
最后
學(xué)無止境阿宅,還有很多細(xì)節(jié),但足以打動面試官脱柱,覺得真是一個很用心的候選人呢... 希望這些能幫到你拉馋。
還有阿里內(nèi)推:這邊是阿里集團(tuán)-淘系技術(shù)部的,總裁帶頭發(fā)起項目随闺,新成立部門蔓腐,業(yè)務(wù)急速擴(kuò)張,目前還有大量的HC散罕,機(jī)會多傀蓉,考慮的聯(lián)系我:
微信:aihehe5211