Java線程池必知必會知識點總結(jié)
1、線程數(shù)使用開發(fā)規(guī)約
阿里巴巴開發(fā)手冊中關(guān)于線程和線程池的使用有如下三條強制規(guī)約
【強制】創(chuàng)建線程或線程池時請指定有意義的線程名稱,方便出錯時回溯。
正例:自定義線程工廠,并且根據(jù)外部特征進行分組,比如,來自同一機房的調(diào)用,把機房編號賦值給whatFeatureOfGroup
public class UserThreadFactory implements ThreadFactory { private final String namePrefix; private final AtomicInteger nextId = new AtomicInteger(1); /** * 定義線程組名稱,在利用 jstack 來排查問題時,非常有幫助 */ UserThreadFactory(String whatFeatureOfGroup) { namePrefix = "From UserThreadFactory's " + whatFeatureOfGroup + "-Worker-"; } @Override public Thread newThread(Runnable task) { String name = namePrefix + nextId.getAndIncrement(); Thread thread = new Thread(null, task, name, 0); System.out.println(thread.getName()); return thread; } }
【強制】線程資源必須通過線程池提供,不允許在應(yīng)用中自行顯式創(chuàng)建線程。
說明:線程池的好處是減少在創(chuàng)建和銷毀線程上所消耗的時間以及系統(tǒng)資源的開銷,解決資源不足的問題。
如果不使用線程池,有可能造成系統(tǒng)創(chuàng)建大量同類線程而導(dǎo)致消耗完內(nèi)存或者“過度切換”的問題。
【強制】線程池不允許使用 Executors 去創(chuàng)建,而是通過 ThreadPoolExecutor 的方式,這
樣的處理方式讓寫的同學(xué)更加明確線程池的運行規(guī)則,規(guī)避資源耗盡的風險。
說明:Executors 返回的線程池對象的弊端如下:
1) FixedThreadPool 和 SingleThreadPool:
允許的請求隊列長度為 Integer.MAX_VALUE,可能會堆積大量的請求,從而導(dǎo)致 OOM。
2) CachedThreadPool:
允許的創(chuàng)建線程數(shù)量為 Integer.MAX_VALUE,可能會創(chuàng)建大量的線程,從而導(dǎo)致 OOM。
2、 ThreadPoolExecutor源碼
1. 構(gòu)造函數(shù)
UML圖:
ThreadPoolExecutor的構(gòu)造函數(shù)共有四個,但最終調(diào)用的都是同一個:
2.核心參數(shù)
corePoolSize => 線程池核心線程數(shù)量
maximumPoolSize => 線程池最大數(shù)量
keepAliveTime => 線程池的工作線程空閑后,保持存活的時間。如果任務(wù)多而且任務(wù)的執(zhí)行時間比較短,可以調(diào)大keepAliveTime,提高線程的利用率。
unit => 時間單位
workQueue => 線程池所使用的緩沖隊列,隊列類型有:
ArrayBlockingQueue,基于數(shù)組結(jié)構(gòu)的有界阻塞隊列,按FIFO(先進先出)原則對任務(wù)進行排序。使用該隊列,線程池中能創(chuàng)建的最大線程數(shù)為maximumPoolSize
LinkedBlockingQueue,基于鏈表結(jié)構(gòu)的無界阻塞隊列,按FIFO(先進先出)原則對任務(wù)進行排序,吞吐量高于ArrayBlockingQueue。使用該隊列,線程池中能創(chuàng)建的最大線程數(shù)為corePoolSize。靜態(tài)工廠方法 Executor.newFixedThreadPool()使用了這個隊列。
SynchronousQueue,一個不存儲元素的阻塞隊列。添加任務(wù)的操作必須等到另一個線程的移除操作,否則添加操作一直處于阻塞狀態(tài)。靜態(tài)工廠方法 Executor.newCachedThreadPool()使用了這個隊列。
PriorityBlokingQueue:一個支持優(yōu)先級的無界阻塞隊列。使用該隊列,線程池中能創(chuàng)建的最大線程數(shù)為corePoolSize。
threadFactory => 線程池創(chuàng)建線程使用的工廠
handler => 線程池對拒絕任務(wù)的處理策略,主要有4種類型的拒絕策略:
AbortPolicy:無法處理新任務(wù)時,直接拋出異常,這是默認策略。
CallerRunsPolicy:用調(diào)用者所在的線程來執(zhí)行任務(wù)。
DiscardOldestPolicy:丟棄阻塞隊列中最靠前的一個任務(wù),并執(zhí)行當前任務(wù)。
DiscardPolicy:直接丟棄任務(wù)。
3.execute()方法
如果當前運行的線程少于corePoolSize,則創(chuàng)建新的工作線程來執(zhí)行任務(wù)(執(zhí)行這一步驟需要獲取全局鎖)。
如果當前運行的線程大于或等于corePoolSize,而且BlockingQueue未滿,則將任務(wù)加入到BlockingQueue中。
如果BlockingQueue已滿,而且當前運行的線程小于maximumPoolSize,則創(chuàng)建新的工作線程來執(zhí)行任務(wù)(執(zhí)行這一步驟需要獲取全局鎖)。
如果當前運行的線程大于或等于maximumPoolSize,任務(wù)將被拒絕,并調(diào)用RejectExecutionHandler.rejectExecution()方法。即調(diào)用飽和策略對任務(wù)進行處理。
3、線程池的工作流程
執(zhí)行邏輯說明:
判斷核心線程數(shù)是否已滿,核心線程數(shù)大小和corePoolSize參數(shù)有關(guān),未滿則創(chuàng)建線程執(zhí)行任務(wù)
若核心線程池已滿,判斷隊列是否滿,隊列是否滿和workQueue參數(shù)有關(guān),若未滿則加入隊列中
若隊列已滿,判斷線程池是否已滿,線程池是否已滿和maximumPoolSize參數(shù)有關(guān),若未滿創(chuàng)建線程執(zhí)行任務(wù)
若線程池已滿,則采用拒絕策略處理無法執(zhí)執(zhí)行的任務(wù),拒絕策略和handler參數(shù)有關(guān)
4、Executors創(chuàng)建返回ThreadPoolExecutor對象(不推薦)
Executors創(chuàng)建返回ThreadPoolExecutor對象的方法共有三種:
1. Executors#newCachedThreadPool => 創(chuàng)建可緩存的線程池
corePoolSize => 0,核心線程池的數(shù)量為0
maximumPoolSize => Integer.MAX_VALUE,可以認為最大線程數(shù)是無限的
keepAliveTime => 60L
unit => 秒
workQueue => SynchronousQueue
弊端:maximumPoolSize => Integer.MAX_VALUE可能會導(dǎo)致OOM
2. Executors#newSingleThreadExecutor => 創(chuàng)建單線程的線程池
SingleThreadExecutor是單線程線程池,只有一個核心線程:
corePoolSize => 1,核心線程池的數(shù)量為1
maximumPoolSize => 1,只可以創(chuàng)建一個非核心線程
keepAliveTime => 0L
unit => 毫秒
workQueue => LinkedBlockingQueue
弊端:LinkedBlockingQueue是長度為Integer.MAX_VALUE的隊列,可以認為是無界隊列,因此往隊列中可以插入無限多的任務(wù),在資源有限的時候容易引起OOM異常
3. Executors#newFixedThreadPool => 創(chuàng)建固定長度的線程池
corePoolSize => 1,核心線程池的數(shù)量為1
maximumPoolSize => 1,只可以創(chuàng)建一個非核心線程
keepAliveTime => 0L
unit => 毫秒
workQueue => LinkedBlockingQueue
它和SingleThreadExecutor類似,唯一的區(qū)別就是核心線程數(shù)不同,并且由于使用的是LinkedBlockingQueue,在資源有限的時候容易引起OOM異常
5、線程池的合理配置
從以下幾個角度分析任務(wù)的特性:
任務(wù)的性質(zhì):CPU 密集型任務(wù)、IO 密集型任務(wù)和混合型任務(wù)。
任務(wù)的優(yōu)先級:高、中、低。
任務(wù)的執(zhí)行時間:長、中、短。
任務(wù)的依賴性:是否依賴其他系統(tǒng)資源,如數(shù)據(jù)庫連接。
任務(wù)性質(zhì)不同的任務(wù)可以用不同規(guī)模的線程池分開處理??梢酝ㄟ^ Runtime.getRuntime().availableProcessors()
方法獲得當前設(shè)備的 CPU 個數(shù)。
CPU 密集型任務(wù):配置盡可能小的線程,如配置 cpu核心數(shù)+1 個線程的線程池。
IO 密集型任務(wù) :由于線程并不是一直在執(zhí)行任務(wù),則配置盡可能多的線程,如2 ∗ Ncpu。
混合型任務(wù):如果可以拆分,則將其拆分成一個 CPU 密集型任務(wù)和一個 IO 密集型任務(wù)。只要這兩個任務(wù)執(zhí)行的時間相差不是太大,那么分解后執(zhí)行的吞吐率要高于串行執(zhí)行的吞吐率;如果這兩個任務(wù)執(zhí)行時間相差太大,則沒必要進行分解。
優(yōu)先級不同的任務(wù)可以使用優(yōu)先級隊列 PriorityBlockingQueue 來處理,它可以讓優(yōu)先級高的任務(wù)先得到執(zhí)行。但是,如果一直有高優(yōu)先級的任務(wù)加入到阻塞隊列中,那么低優(yōu)先級的任務(wù)可能永遠不能執(zhí)行。
執(zhí)行時間不同的任務(wù)可以交給不同規(guī)模的線程池來處理,或者也可以使用優(yōu)先級隊列,讓執(zhí)行時間短的任務(wù)先執(zhí)行。
依賴數(shù)據(jù)庫連接池的任務(wù),因為線程提交 SQL 后需要等待數(shù)據(jù)庫返回結(jié)果,線程數(shù)應(yīng)該設(shè)置得較大,這樣才能更好的利用 CPU。
建議使用有界隊列,有界隊列能增加系統(tǒng)的穩(wěn)定性和預(yù)警能力??梢愿鶕?jù)需要設(shè)大一點,比如幾千。使用無界隊列,線程池的隊列就會越來越大,有可能會撐滿內(nèi)存,導(dǎo)致整個系統(tǒng)不可用。
處理拒絕策略有以下幾種比較推薦:
在程序中捕獲RejectedExecutionException異常,在捕獲異常中對任務(wù)進行處理。針對默認拒絕策略使用CallerRunsPolicy拒絕策略,該策略會將任務(wù)交給調(diào)用execute的線程執(zhí)行【一般為主線程】,此時主線程將在一段時間內(nèi)不能提交任何任務(wù),從而使工作線程處理正在執(zhí)行的任務(wù)。此時提交的線程將被保存在TCP隊列中,TCP隊列滿將會影響客戶端,這是一種平緩的性能降低自定義拒絕策略,只需要實現(xiàn)RejectedExecutionHandler接口即可如果任務(wù)不是特別重要,使用DiscardPolicy和DiscardOldestPolicy拒絕策略將任務(wù)丟棄也是可以的如果使用Executors的靜態(tài)方法創(chuàng)建ThreadPoolExecutor對象,可以通過使用Semaphore對任務(wù)的執(zhí)行進行限流也可以避免出現(xiàn)OOM異常。
6、拒絕策略
有以下幾種比較推薦:
在程序中捕獲RejectedExecutionException異常,在捕獲異常中對任務(wù)進行處理。針對默認拒絕策略
使用CallerRunsPolicy拒絕策略,該策略會將任務(wù)交給調(diào)用execute的線程執(zhí)行【一般為主線程】,此時主線程將在一段時間內(nèi)不能提交任何任務(wù),從而使工作線程處理正在執(zhí)行的任務(wù)。此時提交的線程將被保存在TCP隊列中,TCP隊列滿將會影響客戶端,這是一種平緩的性能降低
自定義拒絕策略,只需要實現(xiàn)RejectedExecutionHandler接口即可
如果任務(wù)不是特別重要,使用DiscardPolicy和DiscardOldestPolicy拒絕策略將任務(wù)丟棄也是可以的如果使用Executors的靜態(tài)方法創(chuàng)建ThreadPoolExecutor對象,可以通過使用Semaphore對任務(wù)的執(zhí)行進行限流也可以避免出現(xiàn)OOM異常。
參考文章:8大拒絕策略
7、線程池的五種運行狀態(tài)
線程狀態(tài):
不同于線程狀態(tài),線程池也有如下幾種 狀態(tài):
• RUNNING :該狀態(tài)的線程池既能接受新提交的任務(wù),又能處理阻塞隊列中任務(wù)。
• SHUTDOWN:該狀態(tài)的線程池不能接收新提交的任務(wù),但是能處理阻塞隊列中的任務(wù)。(政府服務(wù)大廳不在允許群眾拿號了,處理完手頭的和排隊的政務(wù)就下班)
處于 RUNNING 狀態(tài)時,調(diào)用 shutdown()方法會使線程池進入到該狀態(tài)。
注意:finalize() 方法在執(zhí)行過程中也會隱式調(diào)用shutdown()方法。
• STOP:該狀態(tài)的線程池不接受新提交的任務(wù),也不處理在阻塞隊列中的任務(wù),還會中斷正在執(zhí)行的任務(wù)。(政府服務(wù)大廳不再進行服務(wù)了,拿號、排隊、以及手頭工作都停止了。)
在線程池處于 RUNNING 或 SHUTDOWN 狀態(tài)時,調(diào)用shutdownNow() 方法會使線程池進入到該狀態(tài);
• TIDYING:如果所有的任務(wù)都已終止,workerCount (有效線程數(shù))=0。
線程池進入該狀態(tài)后會調(diào)用 terminated() 鉤子方法進入TERMINATED 狀態(tài)。
• TERMINATED:在terminated()鉤子方法執(zhí)行完后進入該狀態(tài),默認terminated()鉤子方法中什么也沒有做。
【參考文章】
【1】《JAVA并發(fā)編程藝術(shù)》
【2】tech.meituan.com/2020/04/02/…
總結(jié)
到此這篇關(guān)于Java線程池必知必會知識點的文章就介紹到這了,更多相關(guān)Java線程池必知必會內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
關(guān)于SpringCloud的微服務(wù)結(jié)構(gòu)及微服務(wù)遠程調(diào)用
Spring Cloud 是一套完整的微服務(wù)解決方案,基于 Spring Boot 框架,準確的說,它不是一個框架,而是一個大的容器,它將市面上較好的微服務(wù)框架集成進來,從而簡化了開發(fā)者的代碼量,需要的朋友可以參考下2023-05-05Java利用TreeUtils工具類實現(xiàn)列表轉(zhuǎn)樹
在開發(fā)過程中,總有列表轉(zhuǎn)樹的需求,幾乎是項目的標配,有沒有一種通用且跨項目的解決方式呢?本文將基于Java8的Lambda?表達式和Stream等知識,使用TreeUtils工具類實現(xiàn)一行代碼完成列表轉(zhuǎn)樹這一通用型需求,需要的可以參考一下2022-11-11Spring Boot應(yīng)用監(jiān)控的實戰(zhàn)教程
Spring Boot 提供運行時的應(yīng)用監(jiān)控和管理功能,下面這篇文章主要給大家介紹了關(guān)于Spring Boot應(yīng)用監(jiān)控的相關(guān)資料,文中通過示例代碼介紹的非常詳細,需要的朋友可以參考借鑒,下面隨著小編來一起學(xué)習學(xué)習吧2018-05-05