更新時間:2019年02月25日15時10分 來源:傳智播客java培訓(xùn) 瀏覽次數(shù):
1. 線程池的優(yōu)點:
合理利用線程池能夠帶來三個好處。第一:降低資源消耗。通過重復(fù)利用已創(chuàng)建的線程降低線程創(chuàng)建和銷毀造成的消耗。第二:提高響應(yīng)速度。當(dāng)任務(wù)到達(dá)時,任務(wù)可以不需要等到線程創(chuàng)建就能立即執(zhí)行。第三:提高線程的可管理性。線程是稀缺資源,如果無限制的創(chuàng)建,不僅會消耗系統(tǒng)資源,還會降低系統(tǒng)的穩(wěn)定性,使用線程池可以進(jìn)行統(tǒng)一的分配,調(diào)優(yōu)和監(jiān)控。但是要做到合理的利用線程池,必須對其原理了如指掌。
2. 線程池框架Executor:
java中的線程池是通過Executor框架實現(xiàn)的,ThreadPoolExecutor:線程池的具體實現(xiàn)類,一般用的各種線程池都是基于這個類實現(xiàn)的。構(gòu)造方法如下:
參數(shù)介紹:
corePoolSize:線程池的核心線程數(shù),線程池中運(yùn)行的線程數(shù)永遠(yuǎn)不會超過 corePoolSize 個,默認(rèn)情況下可以一直存活。
keepAliveTime: 默認(rèn)情況下,只有當(dāng)線程池中的線程數(shù)大于corePoolSize時,keepAliveTime才會起作用,直到線程池中的線程數(shù)不大于corePoolSize,即當(dāng)線程池中的線程數(shù)大于corePoolSize時,如果一個線程空閑的時間達(dá)到keepAliveTime,則會終止,直到線程池中的線程數(shù)不超過corePoolSize。但是如果調(diào)用了allowCoreThreadTimeOut(boolean)方法,在線程池中的線程數(shù)不大于corePoolSize時,keepAliveTime參數(shù)也會起作用,直到線程池中的線程數(shù)為0;所以如果任務(wù)很多,并且每個任務(wù)執(zhí)行的時間比較短,可以調(diào)大這個時間,提高線程的利用率。
maximumPoolSize:線程池允許的最大線程數(shù)。如果隊列滿了,并且已創(chuàng)建的線程數(shù)小于最大線程數(shù),則線程池會再創(chuàng)建新的線程執(zhí)行任務(wù)。值得注意的是如果使用了無界的任務(wù)隊列這個參數(shù)就沒什么效果。
unit :表示 keepAliveTime 的單位;可選的單位有天,小時,分鐘,毫秒。微秒,納秒等。
workQueue:表示存放任務(wù)的阻塞隊列。
BlockingQueue:阻塞隊列(BlockingQueue)是java.util.concurrent下的主要用來控制線程同步的工具。如果BlockQueue是空的,從BlockingQueue取東西的操作將會被阻斷進(jìn)入等待狀態(tài),直到BlockingQueue進(jìn)了東西才會被喚醒。同樣,如果BlockingQueue是滿的,任何試圖往里存東西的操作也會被阻斷進(jìn)入等待狀態(tài),直到BlockingQueue里有空間才會被喚醒繼續(xù)操作。
阻塞隊列常用于生產(chǎn)者和消費者的場景,生產(chǎn)者是往隊列里添加元素的線程,消費者是從隊列里拿元素的線程。阻塞隊列就是生產(chǎn)者存放元素的容器,而消費者也只從容器里拿元素。具體的實現(xiàn)類有LinkedBlockingQueue,ArrayBlockingQueued等。ArrayBlockingQueue:是一個基于數(shù)組結(jié)構(gòu)的有界阻塞隊列,此隊列按 FIFO(先進(jìn)先出)原則對元素進(jìn)行排序。LinkedBlockingQueue:一個基于鏈表結(jié)構(gòu)的阻塞隊列,此隊列按FIFO (先進(jìn)先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue。synchronousQueue:這個隊列比較特殊,它不會保存提交的任務(wù),而是將直接新建一個線程來執(zhí)行新來的任務(wù)
3. 線程池的線程程初始化
默認(rèn)情況下,創(chuàng)建線程池之后,線程池中是沒有線程的,需要提交任務(wù)之后才會創(chuàng)建線程。在實際中如果需要線程池創(chuàng)建之后立即創(chuàng)建線程,可以通過以下兩個方法辦到:
prestartCoreThread():初始化一個核心線程;
prestartAllCoreThreads():初始化所有核心線程
4. worker類
在ThreadPoolExecutor主要Worker類來控制線程的復(fù)用。線程池創(chuàng)建線程時,會將線程封裝成工作線程Worker,Worker在執(zhí)行完任務(wù)后,還會無限循環(huán)獲取工作隊列里的任務(wù)來執(zhí)行。
5. 線程池的工作過程
線程池剛創(chuàng)建時,里面沒有一個線程。任務(wù)隊列是作為參數(shù)傳進(jìn)來的。不過,就算隊列里面有任務(wù),線程池也不會馬上執(zhí)行它們。當(dāng)提交一個新任務(wù)到線程池時,線程池會做如下判斷:
如果正在運(yùn)行的線程數(shù)量小于 corePoolSize,那么馬上創(chuàng)建線程運(yùn)行這個任務(wù);
如果正在運(yùn)行的線程數(shù)量大于或等于 corePoolSize,那么將這個任務(wù)放入隊列;
如果這時候隊列滿了,而且正在運(yùn)行的線程數(shù)量小于 maximumPoolSize,那么還是要創(chuàng)建非核心線程立刻運(yùn)行這個任務(wù);
如果隊列滿了,而且正在運(yùn)行的線程數(shù)量大于或等于 maximumPoolSize,那么會采取任務(wù)拒絕策略,如下:
當(dāng)一個線程完成任務(wù)時,它會從隊列中取下一個任務(wù)來執(zhí)行。
當(dāng)一個線程無事可做,超過一定的時間(keepAliveTime)時,線程池會判斷,如果當(dāng)前運(yùn)行的線程數(shù)大于corePoolSize,那么這個線程就被停掉。所以線程池的所有任務(wù)完成后,它最終會收縮到corePoolSize 的大小。
6. 線程池的關(guān)閉
ThreadPoolExecutor提供了兩個方法,用于線程池的關(guān)閉,分別是shutdown()和shutdownNow(),其中:shutdown():不會立即終止線程池,而是要等所有任務(wù)緩存隊列中的任務(wù)都執(zhí)行完后才終止,但再也不會接受新的任務(wù)。shutdownNow():立即終止線程池,并嘗試打斷正在執(zhí)行的任務(wù),并且清空任務(wù)緩存隊列,返回尚未執(zhí)行的任務(wù)。
7. 線程池容量的動態(tài)調(diào)整
ThreadPoolExecutor提供了動態(tài)調(diào)整線程池容量大小的方法:setCorePoolSize()和setMaximumPoolSize(),
setCorePoolSize:設(shè)置核心池大小
setMaximumPoolSize:設(shè)置線程池最大能創(chuàng)建的線程數(shù)目大小
當(dāng)上述參數(shù)從小變大時,ThreadPoolExecutor進(jìn)行線程賦值,還可能立即創(chuàng)建新的線程來執(zhí)行任務(wù)。
8. 幾種類型的線程池
1) SingleThreadExecutor:單個后臺線程 (其緩沖隊列是LinkedBlockingQueue,無界的)
創(chuàng)建一個單線程的線程池。這個線程池只有一個核心線程在工作,也就是相當(dāng)于單線程串行執(zhí)行所有任務(wù)。如果這個唯一的線程因為異常結(jié)束,那么會有一個新的線程來替代它。此線程池保證所有任務(wù)的執(zhí)行順序按照任務(wù)的提交順序執(zhí)行。
舉例子:比如一個村里只有一口井,每次只能一個人打水,先來先打。
2) FixedThreadPool:只有核心線程的線程池,大小固定(其緩沖隊列是LinkedBlockingQueue,無界的) 。
創(chuàng)建固定大小的線程池。每次提交一個任務(wù)就創(chuàng)建一個線程,直到線程達(dá)到線程池的最大大小。線程池的大小一旦達(dá)到最大值就會保持不變,如果某個線程因為執(zhí)行異常而結(jié)束,那么線程池會補(bǔ)充一個新線程。
舉例子:村里有3口井,大家排隊打井水,可以無數(shù)人排隊,井只有3口,每次最多只能有3個人打井水。沒人打水時,3口井也在那里。由于線程不會回收,F(xiàn)ixThreadPool會更快地響應(yīng)外界請求,這也很容易理解,就好像有人突然想打井水,井不是現(xiàn)用現(xiàn)建的。
3) CachedThreadPool:無界線程池,可以進(jìn)行自動線程回收。(其緩沖隊列是SynchronousQueue,一個是緩沖區(qū)為1的阻塞隊列
如果線程池的大小超過了處理任務(wù)所需要的線程,那么就會回收部分空閑(60秒不執(zhí)行任務(wù))的線程,當(dāng)任務(wù)數(shù)增加時,此線程池又可以智能的添加新線程來處理任務(wù)。此線程池不會對線程池大小做限制,線程池大小完全依賴于操作系統(tǒng)(或者說JVM)能夠創(chuàng)建的最大線程大小。
舉個例子:CachedThreadPool就像是一堆人去一個很大的咖啡館喝咖啡,里面服務(wù)員也很多,隨時去,隨時都可以喝到咖啡。但是為了響應(yīng)國家的“光盤行動”,一個人喝剩下的咖啡會被保留60秒,供新來的客人使用,哈哈哈哈哈,好惡心啊。如果你運(yùn)氣好,沒有剩下的咖啡,你會得到一杯新咖啡。但是以前客人剩下的咖啡超過60秒,就變質(zhì)了,會被服務(wù)員回收掉。比較適合執(zhí)行大量的耗時較少的任務(wù)。喝咖啡人挺多的,喝的時間也不長。
4)ScheduledThreadPool:核心線程池固定,大小無限的線程池。核心線程數(shù)固定,非核心線程(閑著沒活干會被立即回收)數(shù)沒有限制。此線程池支持定時以及周期性執(zhí)行任務(wù)的需求。