上篇《Java線程的6種狀態(tài)詳解及創(chuàng)建線程的4種方式》
前言:我們都知道,線程是稀有資源,系統(tǒng)頻繁創(chuàng)建會很大程度上影響服務器的使用效率凰盔,如果不加以限制,很容易就會把服務器資源耗盡默蚌。所以,我們可以通過創(chuàng)建線程池來管理這些線程,提升對線程的使用率炎辨。
1、什么是線程池丝格?
簡而言之撑瞧,線程池就是管理線程的一個容器,有任務需要處理時显蝌,會相繼判斷核心線程數(shù)是否還有空閑预伺、線程池中的任務隊列是否已滿、是否超過線程池大小曼尊,然后調用或創(chuàng)建線程或者排隊酬诀,線程執(zhí)行完任務后并不會立即被銷毀,而是仍然在線程池中等待下一個任務涩禀,如果超過存活時間還沒有新的任務就會被銷毀料滥,通過這樣復用線程從而降低開銷。
2艾船、使用線程池有什么優(yōu)點葵腹?
可能有人就會問了,使用線程池有什么好處嗎屿岂?那不用說践宴,好處自然是有滴。大概有以下:
1爷怀、提升線程池中線程的使用率阻肩,減少對象的創(chuàng)建、銷毀运授。
2烤惊、線程池的伸縮性對性能有較大的影響,使用線程池可以控制線程數(shù)吁朦,有效的提升服務器的使用資源柒室,避免由于資源不足而發(fā)生宕機等問題。(創(chuàng)建太多線程逗宜,將會浪費一定的資源雄右,有些線程未被充分使用;銷毀太多線程纺讲,將導致之后浪費時間再次創(chuàng)建它們擂仍;創(chuàng)建線程太慢,將會導致長時間的等待熬甚,性能變差逢渔;銷毀線程太慢,導致其它線程資源饑餓乡括。)
3肃廓、線程池的核心工作流程(重要)
我們要使用線程池得先了解它是怎么工作的冲簿,流程如下圖,廢話不多說看圖就行亿昏。核心就是復用線程峦剔,降低開銷。
4角钩、線程池的五種狀態(tài)生命周期
- RUNNING :能接受新提交的任務吝沫,并且也能處理阻塞隊列中的任務。
- SHUTDOWN:關閉狀態(tài)递礼,不再接受新提交的任務惨险,但卻可以繼續(xù)處理阻塞隊列中已保存的任務。在線程池處于 RUNNING 狀態(tài)時脊髓,調用 shutdown() 方法會使線程池進入到該狀態(tài)辫愉。(finalize() 方法在執(zhí)行過程中也會調用 shutdown() 方法進入該狀態(tài))。
- STOP:不能接受新任務将硝,也不處理隊列中的任務恭朗,會中斷正在處理任務的線程。在線程池處于 RUNNING 或 SHUTDOWN 狀態(tài)時依疼,調用 shutdownNow() 方法會使線程池進入到該狀態(tài)痰腮。
- TIDYING:如果所有的任務都已終止了,workerCount (有效線程數(shù)) 為0律罢,線程池進入該狀態(tài)后會調用 terminated() 方法進入 TERMINATED 狀態(tài)膀值。
-
TERMINATED:在 terminated() 方法執(zhí)行完后進入該狀態(tài),默認 terminated() 方法中什么也沒有做误辑。
線程池的生命周期流程圖
5沧踏、創(chuàng)建線程池的幾種方式
- 通過 Executors 工廠方法創(chuàng)建
- 通過 new ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) 自定義創(chuàng)建
相對而言,更建議用第二個創(chuàng)建線程池巾钉,Executors 創(chuàng)建的線程池內(nèi)部很多地方用到了無界任務隊列翘狱,在高并發(fā)場景下,無界任務隊列會接收過多的任務對象睛琳,嚴重情況下會導致 JVM 崩潰盒蟆,一些大廠也是禁止使用 Executors 工廠方法去創(chuàng)建線程池踏烙。newFixedThreadPool 和 newSingleThreadExecutor 的主要問題是堆積的請求處理隊列可能會耗費非常大的內(nèi)存师骗,甚至 OOM;newCachedThreadPool 和 newScheduledThreadPool 的主要問題是線程數(shù)最大數(shù)是 Integer.MAX_VALUE讨惩,可能會創(chuàng)建數(shù)量非常多的線程辟癌,甚至 OOM。
5.1荐捻、Executors 五個工廠方法創(chuàng)建不同線程池的區(qū)別
1黍少、newCachedThreadPool()(工作隊列使用的是 SynchronousQueue)
創(chuàng)建一個線程池寡夹,如果線程池中的線程數(shù)量過大,它可以有效的回收多余的線程厂置,如果線程數(shù)不足菩掏,那么它可以創(chuàng)建新的線程。
不足:這種方式雖然可以根據(jù)業(yè)務場景自動的擴展線程數(shù)來處理我們的業(yè)務昵济,但是最多需要多少個線程同時處理卻是我們無法控制的智绸。
優(yōu)點:如果當?shù)诙€任務開始,第一個任務已經(jīng)執(zhí)行結束访忿,那么第二個任務會復用第一個任務創(chuàng)建的線程瞧栗,并不會重新創(chuàng)建新的線程,提高了線程的復用率海铆。
作用:該方法返回一個可以根據(jù)實際情況調整線程池中線程的數(shù)量的線程池迹恐。即該線程池中的線程數(shù)量不確定,是根據(jù)實際情況動態(tài)調整的卧斟。
2殴边、newFixedThreadPool()(工作隊列使用的是 LinkedBlockingQueue)
這種方式可以指定線程池中的線程數(shù)。如果滿了后又來了新任務珍语,此時只能排隊等待找都。
優(yōu)點:newFixedThreadPool 的線程數(shù)是可以進行控制的,因此我們可以通過控制最大線程來使我們的服務器達到最大的使用率廊酣,同時又可以保證即使流量突然增大也不會占用服務器過多的資源能耻。
作用:該方法返回一個固定線程數(shù)量的線程池,該線程池中的線程數(shù)量始終不變亡驰,即不會再創(chuàng)建新的線程晓猛,也不會銷毀已經(jīng)創(chuàng)建好的線程,自始自終都是那幾個固定的線程在工作凡辱,所以該線程池可以控制線程的最大并發(fā)數(shù)戒职。
3、newScheduledThreadPool()
該線程池支持定時透乾,以及周期性的任務執(zhí)行洪燥,我們可以延遲任務的執(zhí)行時間,也可以設置一個周期性的時間讓任務重復執(zhí)行乳乌。該線程池中有以下兩種延遲的方法捧韵。
scheduleAtFixedRate 不同的地方是任務的執(zhí)行時間,如果間隔時間大于任務的執(zhí)行時間汉操,任務不受執(zhí)行時間的影響再来。如果間隔時間小于任務的執(zhí)行時間,那么任務執(zhí)行結束之后,會立馬執(zhí)行芒篷,至此間隔時間就會被打亂搜变。
scheduleWithFixedDelay 的間隔時間不會受任務執(zhí)行時間長短的影響。
作用:該方法返回一個可以控制線程池內(nèi)線程定時或周期性執(zhí)行某任務的線程池针炉。
4挠他、newSingleThreadExecutor()
這是一個單線程池,至始至終都由一個線程來執(zhí)行篡帕。
作用:該方法返回一個只有一個線程的線程池绩社,即每次只能執(zhí)行一個線程任務,多余的任務會保存到一個任務隊列中赂苗,等待這一個線程空閑愉耙,當這個線程空閑了再按 FIFO 方式順序執(zhí)行任務隊列中的任務。
5拌滋、newSingleThreadScheduledExecutor()
只有一個線程朴沿,用來調度任務在指定時間執(zhí)行。
作用:該方法返回一個可以控制線程池內(nèi)線程定時或周期性執(zhí)行某任務的線程池败砂。只不過和上面的區(qū)別是該線程池大小為 1赌渣,而上面的可以指定線程池的大小。
使用示例:
//創(chuàng)建一個會根據(jù)需要創(chuàng)建新線程的線程池
ExecutorService executor= Executors.newCachedThreadPool();
for (int i = 0; i < 20; i++) {
executor.submit(new Runnable() {
@Override
public void run() {
System.out.println(i);
}
});
}
這五種線程池都是直接或者間接獲取的 ThreadPoolExecutor 實例 昌犹,只是實例化時傳遞的參數(shù)不一樣坚芜。所以如果 Java 提供的線程池滿足不了我們的需求,我們可以通過 ThreadPoolExecutor 構造方法創(chuàng)建自定義線程池斜姥。
5.2鸿竖、ThreadPoolExecutor 構造方法參數(shù)詳解
public ThreadPoolExecutor(
int corePoolSize,//線程池核心線程大小
int maximumPoolSize,//線程池最大線程數(shù)量
long keepAliveTime,//空閑線程存活時間
TimeUnit unit,//空閑線程存活時間單位,一共有七種靜態(tài)屬性(TimeUnit.DAYS天,TimeUnit.HOURS小時,TimeUnit.MINUTES分鐘,TimeUnit.SECONDS秒,TimeUnit.MILLISECONDS毫秒,TimeUnit.MICROSECONDS微妙,TimeUnit.NANOSECONDS納秒)
BlockingQueue<Runnable> workQueue,//工作隊列
ThreadFactory threadFactory,//線程工廠铸敏,主要用來創(chuàng)建線程(默認的工廠方法是:Executors.defaultThreadFactory()對線程進行安全檢查并命名)
RejectedExecutionHandler handler//拒絕策略(默認是:ThreadPoolExecutor.AbortPolicy不執(zhí)行并拋出異常)
)
使用示例:
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 20, 2, TimeUnit.SECONDS, new LinkedBlockingQueue<>(5));
5.2.1缚忧、工作隊列
jdk 中提供了四種工作隊列:
①ArrayBlockingQueue
基于數(shù)組的有界阻塞隊列,按 FIFO 排序杈笔。新任務進來后闪水,會放到該隊列的隊尾,有界的數(shù)組可以防止資源耗盡問題蒙具。當線程池中線程數(shù)量達到 corePoolSize 后球榆,再有新任務進來,則會將任務放入該隊列的隊尾禁筏,等待被調度持钉。如果隊列已經(jīng)是滿的,則創(chuàng)建一個新線程融师,如果線程數(shù)量已經(jīng)達到 maxPoolSize右钾,則會執(zhí)行拒絕策略蚁吝。
②LinkedBlockingQuene
基于鏈表的無界阻塞隊列(其實最大容量為 Interger.MAX_VALUE)旱爆,按照 FIFO 排序舀射。由于該隊列的近似無界性,當線程池中線程數(shù)量達到 corePoolSize 后怀伦,再有新任務進來脆烟,會一直存入該隊列,而不會去創(chuàng)建新線程直到 maxPoolSize房待,因此使用該工作隊列時邢羔,參數(shù) maxPoolSize 其實是不起作用的。
③SynchronousQuene
一個不緩存任務的阻塞隊列桑孩,生產(chǎn)者放入一個任務必須等到消費者取出這個任務拜鹤。也就是說新任務進來時,不會緩存流椒,而是直接被調度執(zhí)行該任務敏簿,如果沒有可用線程,則創(chuàng)建新線程宣虾,如果線程數(shù)量達到 maxPoolSize惯裕,則執(zhí)行拒絕策略。
④PriorityBlockingQueue
具有優(yōu)先級的無界阻塞隊列绣硝,優(yōu)先級通過參數(shù) Comparator 實現(xiàn)蜻势。
5.2.2、拒絕策略
當工作隊列中的任務已到達最大限制鹉胖,并且線程池中的線程數(shù)量也達到最大限制握玛,這時如果有新任務提交進來,就會執(zhí)行拒絕策略甫菠。jdk中提供了4中拒絕策略:
①ThreadPoolExecutor.CallerRunsPolicy
該策略下败许,在調用者線程中直接執(zhí)行被拒絕任務的 run 方法,除非線程池已經(jīng) shutdown淑蔚,則直接拋棄任務市殷。
②ThreadPoolExecutor.AbortPolicy
該策略下,直接丟棄任務刹衫,并拋出 RejectedExecutionException 異常醋寝。
③ThreadPoolExecutor.DiscardPolicy
該策略下,直接丟棄任務带迟,什么都不做音羞。
④ThreadPoolExecutor.DiscardOldestPolicy
該策略下,拋棄進入隊列最早的那個任務仓犬,然后嘗試把這次拒絕的任務放入隊列嗅绰。
除此之外,還可以根據(jù)應用場景需要來實現(xiàn) RejectedExecutionHandler 接口自定義策略。
6窘面、線程池的關閉
- shutdown():
1翠语、調用之后不允許繼續(xù)往線程池內(nèi)添加線程;
2、線程池的狀態(tài)變?yōu)?SHUTDOWN 狀態(tài);
3财边、所有在調用 shutdown() 方法之前提交到 ExecutorSrvice 的任務都會執(zhí)行;
4肌括、一旦所有線程結束執(zhí)行當前任務,ExecutorService 才會真正關閉酣难。 - shutdownNow():
1谍夭、該方法返回尚未執(zhí)行的 task 的 List;
2、線程池的狀態(tài)變?yōu)?STOP 狀態(tài);
3憨募、嘗試停止所有的正在執(zhí)行或暫停任務的線程紧索。
簡單點來說,就是:
shutdown() 調用后菜谣,不可以再 submit 新的 task齐板,已經(jīng) submit 的將繼續(xù)執(zhí)行
shutdownNow() 調用后,試圖停止當前正在執(zhí)行的 task葛菇,并返回尚未執(zhí)行的 task 的 list
7甘磨、總結
本文簡單介紹了線程池的一些相關知識,相信大家對線程池的優(yōu)點眯停,線程池的生命周期济舆,線程池的工作流程及線程池的使用有了一個大概的了解,也希望能對有需要的人提供一點幫助莺债!文中有錯誤的地方滋觉,還請留言給予指正,謝謝~