背景
作為幾年工作經(jīng)驗(yàn)的java程序員肯定知道java中通過(guò)線程池來(lái)調(diào)度線程的。線程池分為幾種需五,為什么會(huì)設(shè)計(jì)這幾種線程池各自的實(shí)現(xiàn)算法是什么讼撒,適用場(chǎng)景是什么?這些疑問(wèn)其實(shí)是脫離java語(yǔ)言摹闽,其他語(yǔ)言設(shè)計(jì)線程池也會(huì)遇到同樣的問(wèn)題蹄咖。所以這里對(duì)這線程池設(shè)計(jì)原理需要考慮的方面進(jìn)行分析。
線程池的作用
1.線程池即預(yù)先創(chuàng)建線程的技術(shù)付鹿,一個(gè)線程執(zhí)行完后重新放回不會(huì)銷毀掉提高了線程的利用率澜汤。
2.由于我們要使用線程來(lái)執(zhí)行任務(wù)的時(shí)候直接從線程池中去現(xiàn)成的所以提高了程序的相應(yīng)速度蚜迅。
3.線程池可以對(duì)里面的線程進(jìn)行管理,至于如何管理XXXX(如何銷毀線程俊抵、如何結(jié)束線程狀態(tài)等等)谁不。
創(chuàng)建線程池需要考慮的
從這里我們知道線程池的一些基本配置參數(shù)。比如 線程池的大小徽诲,執(zhí)行任務(wù)隊(duì)列刹帕,線程池滿了新任務(wù)的執(zhí)行策略,工作線程空閑后存活時(shí)間(如果想提高線程利用率提議調(diào)大該時(shí)間)谎替。
所以它的構(gòu)造函數(shù)為:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue)
這里為什么有一個(gè)corePoolSize和maximumPoolSize呢偷溺? 級(jí)別關(guān)系是 coreSize -> 隊(duì)列 ->(無(wú)法使用隊(duì)列則創(chuàng)建新線程) maximumPoolSize。
a).corePoolSize意思是基本大小钱贯,比如線程池corePoolSize=10挫掏,而此時(shí)線程池里有5個(gè)線程且都是空閑的,由于還沒(méi)有達(dá)到corePoolSize秩命,如果提交一個(gè)任務(wù)會(huì)從線程池里選擇一個(gè)線程來(lái)執(zhí)行任務(wù)砍濒。當(dāng)達(dá)到了corePoolSize時(shí)Executor默認(rèn)會(huì)先把任務(wù)添加進(jìn)隊(duì)列中,如果無(wú)法加入隊(duì)列則創(chuàng)建新線程直到達(dá)到maximumPoolSize硫麻。
b).maximumPoolSize使用場(chǎng)景爸邢,如果線程池里的線程數(shù)量達(dá)到了maximumPoolSize且其中的線程沒(méi)有空閑的。當(dāng)新任務(wù)到達(dá)的時(shí)候會(huì)新建線程拿愧,如果無(wú)限地創(chuàng)建會(huì)消耗系統(tǒng)的資源杠河,所以這里有一個(gè)maximumPoolSize參數(shù),當(dāng)線程數(shù)量達(dá)到maximumPoolSize的時(shí)候即時(shí)沒(méi)有空閑線程了也不會(huì)重新創(chuàng)建線程浇辜。
不重新創(chuàng)建線程那怎么辦呢券敌?這就需要使用RejectedExecutionHandler(飽和策略)。現(xiàn)有的飽和策略有,策略分兩種執(zhí)行與不執(zhí)行:
對(duì)于不執(zhí)行的柳洋,我可能會(huì)有以下情況:a.丟棄 b.拋出異常 c.丟棄但是記錄日志或持久化到數(shù)據(jù)庫(kù)(通過(guò)實(shí)現(xiàn)RejectedExecutionHandler接口來(lái)處理)待诅。
對(duì)于執(zhí)行該任務(wù)會(huì)有如下的情況:a.騰出空間,替換最老未執(zhí)行的任務(wù)熊镣。
1).丟棄該任務(wù) 2).丟棄最老未執(zhí)行的騰出空間執(zhí)行該任務(wù)
加入隊(duì)列的幾種情況
當(dāng)我們創(chuàng)建線程池需要指定隊(duì)列的時(shí)候必須卑雁,而不同隊(duì)列線程池會(huì)有不同的表現(xiàn)。
有3種常見(jiàn)的隊(duì)列:
a).ArrayBlockingQueue 有界隊(duì)列绪囱,創(chuàng)建時(shí)候必須制定大小(構(gòu)造函數(shù)要求制定)
b).LinkedBlockingQueue 無(wú)界隊(duì)列
public LinkedBlockingQueue() {
}
c).SynchronousQueue 同步隊(duì)列测蹲,每新增一個(gè)任務(wù)的線程必須等待另一個(gè)線程取出任務(wù)。 //還是不是很理解同步隊(duì)列怎么實(shí)現(xiàn)的背后的實(shí)現(xiàn)原理-怎么做到同步的鬼吵。
這3種隊(duì)列的使用場(chǎng)景是什么扣甲?
當(dāng)資源有限的時(shí)候使用有界隊(duì)列,使用有界隊(duì)列的過(guò)程中齿椅,隊(duì)列大小和最大池大小可能需要互相折衷琉挖。大隊(duì)列小線程池大小可以降低CPU使用率和線程之間的切換启泣。
使用無(wú)界隊(duì)列時(shí)候maxSize參數(shù)無(wú)用,因?yàn)楫?dāng)線程數(shù)超過(guò)coresize的時(shí)候會(huì)一直不停的往LinkedBlockingQueue里放示辈。這個(gè)可以用于web服務(wù)器訪問(wèn)量突發(fā)的情況寥茫。
線程池如何處理任務(wù)
這里講線程池如何提交任務(wù),任務(wù)提交后如何跟蹤結(jié)果顽耳。
execute方式提交坠敷,這里沒(méi)有返回結(jié)果妙同。所以無(wú)法獲取任務(wù)執(zhí)行結(jié)果射富。
public void execute(Runnable command) {
......
addWorke(command,true)
}
private boolean addWorker(Runnable firstTask, boolean core) {
w = new Worker(firstTask); //這里會(huì)把Runable接口包裝城worker接口
works.add(w);
}
*addWorker 怎么判斷線程池已經(jīng)滿了涉及到二進(jìn)制操作,以后專門寫博客來(lái)闡述粥帚。
submit()方式提交可以通過(guò)future獲取任務(wù)執(zhí)行結(jié)果胰耗,當(dāng)調(diào)用future.get()時(shí)候如果任務(wù)執(zhí)行未完成則會(huì)阻塞。
<T> Future<T> submit(Callable<T> task);
線程池如何關(guān)閉
線程池關(guān)閉的時(shí)候需要考慮其所處的狀態(tài)芒涡,即如果有任務(wù)未執(zhí)行完怎么辦柴灯?什么時(shí)候應(yīng)該關(guān)閉線程池。
常見(jiàn)的辦法就是一個(gè)個(gè)遍歷線程费尽,如果不等待執(zhí)行完就sotp停止線程或者中斷現(xiàn)在執(zhí)行的線程赠群。
線程池關(guān)閉的狀態(tài)中有幾個(gè)中間狀態(tài)可以根據(jù) 隊(duì)列只否有正在執(zhí)行的線程,有的話是否繼續(xù)執(zhí)行來(lái)劃分旱幼。
線程池的狀態(tài)有:Running 可以接收新的任務(wù)和執(zhí)行隊(duì)列任務(wù),shutdown 不接收新的任務(wù)和已有隊(duì)列任務(wù)還需要執(zhí)行,stop 不接收新任務(wù)且 已有隊(duì)列任務(wù)也停止(interrupt in-process task),terminate 線程池已經(jīng)停止了
這里shutdown()與shutdownNow的區(qū)別就是shutdown只會(huì)interrputIdleWork查描,即只會(huì)終端沒(méi)有非運(yùn)行時(shí)的線程,正在執(zhí)行的線程等待執(zhí)行完柏卤。
代碼區(qū)別如下:
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(STOP);
interruptWorkers();
tasks = drainQueue();
} finally {
mainLock.unlock();
}
tryTerminate();
return tasks;
}
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(SHUTDOWN);
interruptIdleWorkers();
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
tryTerminate();
}
使用篇
前面介紹了線程池的基本功能冬三,這里就對(duì)其如何使用進(jìn)行分析。
使用涉及到配置缘缚、啟動(dòng)勾笆、狀態(tài)監(jiān)控
線程池的配置選型
即對(duì)各種類型的任務(wù)使用什么樣的策略。我暫時(shí)想到的任務(wù)類型劃分標(biāo)準(zhǔn)有桥滨,1.執(zhí)行時(shí)間長(zhǎng)短 2.優(yōu)先級(jí) 3.cpu型的還是IO型的 4.任務(wù)是否依賴其他特性
原則是對(duì)于CPU秘籍型的任務(wù)窝爪,線程池內(nèi)的線程不宜過(guò)多避免頻繁切換,可以設(shè)置為 N cpu+1
對(duì)于IO密集型的任務(wù)齐媒,線程池內(nèi)的線程可以設(shè)置為2*N cpu
對(duì)于優(yōu)先級(jí)可以使用PriorityBlockingQueue隊(duì)列酸舍,但是如果一直有高優(yōu)先級(jí)的任務(wù)那么低優(yōu)先級(jí)的任務(wù)永遠(yuǎn)執(zhí)行不了。
對(duì)于執(zhí)行時(shí)間過(guò)長(zhǎng)的(比如數(shù)據(jù)庫(kù))需要一定時(shí)間才能返回所以空閑時(shí)間比較長(zhǎng)里初,這樣的話可以把線程數(shù)量設(shè)置大一些啃勉。
線程池的監(jiān)控
我們想監(jiān)控線程池所有線程是否執(zhí)行完,線程池里的線程使用狀態(tài)双妨,已經(jīng)完成的線程數(shù)淮阐。
ThreadPoolExecutor提供了一些變量來(lái)存儲(chǔ)線程池的狀態(tài)叮阅,比如taskCount(線程池),completedTaskCount,largestPoolSize(曾經(jīng)創(chuàng)建過(guò)的最大線程數(shù))泣特。
public class ThreadPoolExecutor extends AbstractExecutorService {
private int largestPoolSize;
private long completedTaskCount;
}
寫完后的想法
1.通過(guò)從線程池的維度主動(dòng)檢索其知識(shí)來(lái)理解execute(),submit()方法浩姥,這種方式層次清晰,在信息維度就是從高緯往低維度去找是一種好的方式状您。
2.任何框架的描述都可以自己提出很多的問(wèn)題勒叠, 原理、結(jié)構(gòu)膏孟、如何使用等等眯分,通過(guò)這些提問(wèn)來(lái)掌握知識(shí)是一種很好的辦法,從另一角度來(lái)說(shuō)你能夠提出多少問(wèn)題你對(duì)這一領(lǐng)域抽象層次就了解多少柒桑。
3.至于各種線程池還沒(méi)有闡述分析等待下一篇吧弊决。