淺談Java線程池的7大核心參數(shù)
前言
java中經(jīng)常需要用到多線程來(lái)處理一些業(yè)務(wù),我不建議單純使用繼承Thread或者實(shí)現(xiàn)Runnable接口的方式來(lái)創(chuàng)建線程,那樣勢(shì)必有創(chuàng)建及銷(xiāo)毀線程耗費(fèi)資源、線程上下文切換問(wèn)題。
同時(shí)創(chuàng)建過(guò)多的線程也可能引發(fā)資源耗盡的風(fēng)險(xiǎn),這個(gè)時(shí)候引入線程池比較合理,方便線程任務(wù)的管理。
java中涉及到線程池的相關(guān)類(lèi)均在jdk1.5開(kāi)始的java.util.concurrent包中,涉及到的幾個(gè)核心類(lèi)及接口包括:
Executor、Executors、ExecutorService、ThreadPoolExecutor、FutureTask、Callable、Runnable等。
一、線程池的創(chuàng)建及重要參數(shù)
線程池可以自動(dòng)創(chuàng)建也可以手動(dòng)創(chuàng)建,自動(dòng)創(chuàng)建體現(xiàn)在Executors工具類(lèi)中,常見(jiàn)的可以創(chuàng)建newFixedThreadPool、newCachedThreadPool、newSingleThreadExecutor、newScheduledThreadPool;
手動(dòng)創(chuàng)建體現(xiàn)在可以靈活設(shè)置線程池的各個(gè)參數(shù),體現(xiàn)在代碼中即ThreadPoolExecutor類(lèi)構(gòu)造器上各個(gè)實(shí)參的不同:
public static ExecutorService newFixedThreadPool(int var0) { return new ThreadPoolExecutor(var0, var0, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()); } public static ExecutorService newSingleThreadExecutor() { return new Executors.FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue())); } public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, 2147483647, 60L, TimeUnit.SECONDS, new SynchronousQueue()); } public static ScheduledExecutorService newScheduledThreadPool(int var0) { return new ScheduledThreadPoolExecutor(var0); }
(重點(diǎn))
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {……}
二、ThreadPoolExecutor中重要的幾個(gè)參數(shù)詳解
- corePoolSize:核心線程數(shù),也是線程池中常駐的線程數(shù),線程池初始化時(shí)默認(rèn)是沒(méi)有線程的,當(dāng)任務(wù)來(lái)臨時(shí)才開(kāi)始創(chuàng)建線程去執(zhí)行任務(wù)
- maximumPoolSize:最大線程數(shù),在核心線程數(shù)的基礎(chǔ)上可能會(huì)額外增加一些非核心線程,需要注意的是只有當(dāng)workQueue隊(duì)列填滿時(shí)才會(huì)創(chuàng)建多于corePoolSize的線程(線程池總線程數(shù)不超過(guò)maxPoolSize)
- keepAliveTime:非核心線程的空閑時(shí)間超過(guò)keepAliveTime就會(huì)被自動(dòng)終止回收掉,注意當(dāng)corePoolSize=maxPoolSize時(shí),keepAliveTime參數(shù)也就不起作用了(因?yàn)椴淮嬖诜呛诵木€程);
- unit:keepAliveTime的時(shí)間單位
- workQueue:用于保存任務(wù)的隊(duì)列,可以為無(wú)界、有界、同步移交三種隊(duì)列類(lèi)型之一,當(dāng)池子里的工作線程數(shù)大于corePoolSize時(shí),這時(shí)新進(jìn)來(lái)的任務(wù)會(huì)被放到隊(duì)列中
- threadFactory:創(chuàng)建線程的工廠類(lèi),默認(rèn)使用Executors.defaultThreadFactory(),也可以使用guava庫(kù)的ThreadFactoryBuilder來(lái)創(chuàng)建
- handler:線程池?zé)o法繼續(xù)接收任務(wù)(隊(duì)列已滿且線程數(shù)達(dá)到maximunPoolSize)時(shí)的飽和策略,取值有AbortPolicy、CallerRunsPolicy、DiscardOldestPolicy、DiscardPolicy
線程池中的線程創(chuàng)建流程圖:
(基于<Java并發(fā)編程的藝術(shù)>一書(shū))
舉個(gè)例子:
現(xiàn)有一個(gè)線程池,corePoolSize=10,maxPoolSize=20,隊(duì)列長(zhǎng)度為100,那么當(dāng)任務(wù)過(guò)來(lái)會(huì)先創(chuàng)建10個(gè)核心線程數(shù),接下來(lái)進(jìn)來(lái)的任務(wù)會(huì)進(jìn)入到隊(duì)列中直到隊(duì)列滿了,會(huì)創(chuàng)建額外的線程來(lái)執(zhí)行任務(wù)(最多20個(gè)線程),這個(gè)時(shí)候如果再來(lái)任務(wù)就會(huì)執(zhí)行拒絕策略。
三、workQueue隊(duì)列(阻塞隊(duì)列)
SynchronousQueue(同步移交隊(duì)列):隊(duì)列不作為任務(wù)的緩沖方式,可以簡(jiǎn)單理解為隊(duì)列長(zhǎng)度為零LinkedBlockingQueue(無(wú)界隊(duì)列):隊(duì)列長(zhǎng)度不受限制,當(dāng)請(qǐng)求越來(lái)越多時(shí)(任務(wù)處理速度跟不上任務(wù)處理速度造成請(qǐng)求堆積)可能導(dǎo)致內(nèi)存占用過(guò)多或OOMArrayBlockintQueue(有界隊(duì)列):隊(duì)列長(zhǎng)度受限,當(dāng)隊(duì)列滿了就需要?jiǎng)?chuàng)建多余的線程來(lái)執(zhí)行任務(wù)
四、常見(jiàn)的幾種自動(dòng)創(chuàng)建線程池方式
自動(dòng)創(chuàng)建線程池的幾種方式都封裝在Executors工具類(lèi)中:
newFixedThreadPool:使用的構(gòu)造方式為new ThreadPoolExecutor(var0, var0, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()),設(shè)置了corePoolSize=maxPoolSize,keepAliveTime=0(此時(shí)該參數(shù)沒(méi)作用),無(wú)界隊(duì)列,任務(wù)可以無(wú)限放入,當(dāng)請(qǐng)求過(guò)多時(shí)(任務(wù)處理速度跟不上任務(wù)提交速度造成請(qǐng)求堆積)可能導(dǎo)致占用過(guò)多內(nèi)存或直接導(dǎo)致OOM異常
newSingleThreadExector:使用的構(gòu)造方式為new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(), var0),基本同newFixedThreadPool,但是將線程數(shù)設(shè)置為了1,單線程,弊端和newFixedThreadPool一致
newCachedThreadPool: 使用的構(gòu)造方式為new ThreadPoolExecutor(0, 2147483647, 60L, TimeUnit.SECONDS, new SynchronousQueue()),corePoolSize=0,maxPoolSize為很大的數(shù),同步移交隊(duì)列,也就是說(shuō)不維護(hù)常駐線程(核心線程),每次來(lái)請(qǐng)求直接創(chuàng)建新線程來(lái)處理任務(wù),也不使用隊(duì)列緩沖,會(huì)自動(dòng)回收多余線程,由于將maxPoolSize設(shè)置成Integer.MAX_VALUE,當(dāng)請(qǐng)求很多時(shí)就可能創(chuàng)建過(guò)多的線程,導(dǎo)致資源耗盡OOM
newScheduledThreadPool:使用的構(gòu)造方式為new ThreadPoolExecutor(var1, 2147483647, 0L, TimeUnit.NANOSECONDS, new ScheduledThreadPoolExecutor.DelayedWorkQueue()),支持定時(shí)周期性執(zhí)行,注意一下使用的是延遲隊(duì)列,弊端同newCachedThreadPool一致
所以根據(jù)上面分析我們可以看到,F(xiàn)ixedThreadPool和SigleThreadExecutor中之所以用LinkedBlockingQueue無(wú)界隊(duì)列,是因?yàn)樵O(shè)置了corePoolSize=maxPoolSize,線程數(shù)無(wú)法動(dòng)態(tài)擴(kuò)展,于是就設(shè)置了無(wú)界阻塞隊(duì)列來(lái)應(yīng)對(duì)不可知的任務(wù)量;而CachedThreadPool則使用的是SynchronousQueue同步移交隊(duì)列,為什么使用這個(gè)隊(duì)列呢?
因?yàn)镃achedThreadPool設(shè)置了corePoolSize=0,maxPoolSize=Integer.MAX_VALUE,來(lái)一個(gè)任務(wù)就創(chuàng)建一個(gè)線程來(lái)執(zhí)行任務(wù),用不到隊(duì)列來(lái)存儲(chǔ)任務(wù);SchduledThreadPool用的是延遲隊(duì)列DelayedWorkQueue。在實(shí)際項(xiàng)目開(kāi)發(fā)中也是推薦使用手動(dòng)創(chuàng)建線程池的方式,而不用默認(rèn)方式。
關(guān)于這點(diǎn)在《阿里巴巴開(kāi)發(fā)規(guī)范》中是這樣描述的:
handler拒絕策略
- AbortPolicy:中斷拋出異常
- DiscardPolicy:默默丟棄任務(wù),不進(jìn)行任何通知
- DiscardOldestPolicy:丟棄掉在隊(duì)列中存在時(shí)間最久的任務(wù)
- CallerRunsPolicy:讓提交任務(wù)的線程去執(zhí)行任務(wù)(對(duì)比前三種比較友好一丟丟
關(guān)閉線程池
- shutdownNow():立即關(guān)閉線程池(暴力),正在執(zhí)行中的及隊(duì)列中的任務(wù)會(huì)被中斷,同時(shí)該方法會(huì)返回被中斷的隊(duì)列中的任務(wù)列表
- shutdown():平滑關(guān)閉線程池,正在執(zhí)行中的及隊(duì)列中的任務(wù)能執(zhí)行完成,后續(xù)進(jìn)來(lái)的任務(wù)會(huì)被執(zhí)行拒絕策略i
- sTerminated():當(dāng)正在執(zhí)行的任務(wù)及對(duì)列中的任務(wù)全部都執(zhí)行(清空)完就會(huì)返回true。
五、線程池實(shí)現(xiàn)線程復(fù)用的原理
1.線程池里執(zhí)行的是任務(wù),核心邏輯在ThreadPoolExecutor類(lèi)的execute方法中,同時(shí)ThreadPoolExecutor中維護(hù)了HashSet<Worker> workers;
2.addWorker()方法來(lái)創(chuàng)建線程執(zhí)行任務(wù),如果是核心線程的任務(wù),會(huì)賦值給Worker的firstTask屬性;
3.Worker實(shí)現(xiàn)了Runnable,本質(zhì)上也是任務(wù),核心在run()方法里;
4.run()方法的執(zhí)行核心runWorker(),自旋拿任務(wù)while (task != null || (task = getTask()) != null)),task是核心線程Worker的firstTask或者getTask();
5.getTask()的核心邏輯:
1.若當(dāng)前工作線程數(shù)量大于核心線程數(shù)->說(shuō)明此線程是非核心工作線程,通過(guò)poll()拿任務(wù),未拿到任務(wù)即getTask()返回null,然后會(huì)在processWorkerExit(w, completedAbruptly)方法釋放掉這個(gè)非核心工作線程的引用;
2.若當(dāng)前工作線程數(shù)量小于核心線程數(shù)->說(shuō)明此時(shí)線程是核心工作線程,通過(guò)take()拿任務(wù)
3.take()方式取任務(wù),如果隊(duì)列中沒(méi)有任務(wù)了會(huì)調(diào)用await()阻塞當(dāng)前線程,直到新任務(wù)到來(lái),所以核心工作線程不會(huì)被回收; 當(dāng)執(zhí)行execute方法里的workQueue.offer(command)時(shí)會(huì)調(diào)用Condition.singal()方法喚醒一個(gè)之前阻塞的線程,這樣核心線程即可復(fù)用
六、手動(dòng)創(chuàng)建線程池(推薦)
那么上面說(shuō)了使用Executors工具類(lèi)創(chuàng)建的線程池有隱患,那如何使用才能避免這個(gè)隱患呢?對(duì)癥下藥,建立自己的線程工廠類(lèi),靈活設(shè)置關(guān)鍵參數(shù):
//這里默認(rèn)拒絕策略為AbortPolicy private static ExecutorService executor = new ThreadPoolExecutor(10,10,60L, TimeUnit.SECONDS,new ArrayBlockingQueue(10));
使用guava包中的ThreadFactoryBuilder工廠類(lèi)來(lái)構(gòu)造線程池:
private static ThreadFactory threadFactory = new ThreadFactoryBuilder().build(); private static ExecutorService executorService = new ThreadPoolExecutor(10, 10, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(10), threadFactory, new ThreadPoolExecutor.AbortPolicy());
通過(guò)guava的ThreadFactory工廠類(lèi)還可以指定線程組名稱(chēng),這對(duì)于后期定位錯(cuò)誤時(shí)也是很有幫助的
ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("thread-pool-d%").build();
七、Springboot中使用線程池
springboot可以說(shuō)是非常流行了,下面說(shuō)說(shuō)如何在springboot中優(yōu)雅的使用線程池
/** * @ClassName ThreadPoolConfig * @Description 配置類(lèi)中構(gòu)建線程池實(shí)例,方便調(diào)用 * @Author ww * @Date 2021/5/11 * Version 1.0 */ @Configuration public class ThreadPoolConfig { @Bean(value = "threadPoolInstance") public ExecutorService createThreadPoolInstance() { //通過(guò)guava類(lèi)庫(kù)的ThreadFactoryBuilder來(lái)實(shí)現(xiàn)線程工廠類(lèi)并設(shè)置線程名稱(chēng) ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("thread-pool-%d").build(); ExecutorService threadPool = new ThreadPoolExecutor(10, 16, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(100), threadFactory, new ThreadPoolExecutor.AbortPolicy()); return threadPool; } }
/** * @ClassName ThreadPoolConfig * @Description 配置類(lèi)中構(gòu)建線程池實(shí)例,方便調(diào)用 * @Author ww * @Date 2021/5/11 * Version 1.0 */ @Configuration public class ThreadPoolConfig { @Bean(value = "threadPoolInstance") public ExecutorService createThreadPoolInstance() { //通過(guò)guava類(lèi)庫(kù)的ThreadFactoryBuilder來(lái)實(shí)現(xiàn)線程工廠類(lèi)并設(shè)置線程名稱(chēng) ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("thread-pool-%d").build(); ExecutorService threadPool = new ThreadPoolExecutor(10, 16, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(100), threadFactory, new ThreadPoolExecutor.AbortPolicy()); return threadPool; } }
其它相關(guān)
在ThreadPoolExecutor類(lèi)中有兩個(gè)比較重要的方法引起了我的注意:beforeExecute和afterExecute
protected void beforeExecute(Thread var1, Runnable var2) { } protected void afterExecute(Runnable var1, Throwable var2) { }
這兩個(gè)方法是protected修飾的,很顯然是留給開(kāi)發(fā)人員去重寫(xiě)方法體實(shí)現(xiàn)自己的業(yè)務(wù)邏輯,非常適合做鉤子函數(shù),在任務(wù)run方法的前后增加業(yè)務(wù)邏輯,比如添加日志、統(tǒng)計(jì)等。
這個(gè)和我們springmvc中攔截器的preHandle和afterCompletion方法很類(lèi)似,都是對(duì)方法進(jìn)行環(huán)繞,類(lèi)似于spring的AOP。
到此這篇關(guān)于淺談Java線程池的7大核心參數(shù)的文章就介紹到這了,更多相關(guān)Java線程池核心參數(shù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java 將一個(gè)字符重復(fù)n遍過(guò)程詳解
這篇文章主要介紹了Java 將一個(gè)字符重復(fù)n遍過(guò)程詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-10-10linux的shell命令檢測(cè)某個(gè)java程序是否執(zhí)行
ps -ef |grep java|grep2016-04-04利用Java實(shí)現(xiàn)調(diào)用http請(qǐng)求
在實(shí)際開(kāi)發(fā)過(guò)程中,我們經(jīng)常需要調(diào)用對(duì)方提供的接口或測(cè)試自己寫(xiě)的接口是否合適。本文就為大家準(zhǔn)備了幾個(gè)java調(diào)用http請(qǐng)求的幾種常見(jiàn)方式,需要的可以參考一下2022-08-08詳解快速排序算法中的區(qū)間劃分法及Java實(shí)現(xiàn)示例
這篇文章主要介紹了詳解快速排序算法中的區(qū)間劃分法及Java實(shí)現(xiàn)示例,文中分別介紹了快排時(shí)兩種區(qū)間劃分的思路,需要的朋友可以參考下2016-04-04基于Java將Excel科學(xué)計(jì)數(shù)法解析成數(shù)字
這篇文章主要介紹了基于Java將Excel科學(xué)計(jì)數(shù)法解析成數(shù)字,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-09-09基于Java堆內(nèi)存的10個(gè)要點(diǎn)的總結(jié)分析
本篇文章是對(duì)Java堆內(nèi)存的10個(gè)要點(diǎn)進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05解決springboot生成bean名稱(chēng)沖突(AnnotationBeanNameGenerator)
這篇文章主要介紹了解決springboot生成bean名稱(chēng)沖突(AnnotationBeanNameGenerator),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03基于Java設(shè)計(jì)一個(gè)高并發(fā)的秒殺系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了如何基于Java設(shè)計(jì)一個(gè)高并發(fā)的秒殺系統(tǒng),文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,有需要的小伙伴可以參考下2023-10-10