- 資訊首頁(yè) > 開(kāi)發(fā)技術(shù) >
- 淺談Java線(xiàn)程池的7大核心參數
java中經(jīng)常需要用到多線(xiàn)程來(lái)處理一些業(yè)務(wù),我不建議單純使用繼承Thread或者實(shí)現Runnable接口的方式來(lái)創(chuàng )建線(xiàn)程,那樣勢必有創(chuàng )建及銷(xiāo)毀線(xiàn)程耗費資源、線(xiàn)程上下文切換問(wèn)題。
同時(shí)創(chuàng )建過(guò)多的線(xiàn)程也可能引發(fā)資源耗盡的風(fēng)險,這個(gè)時(shí)候引入線(xiàn)程池比較合理,方便線(xiàn)程任務(wù)的管理。
java中涉及到線(xiàn)程池的相關(guān)類(lèi)均在jdk1.5開(kāi)始的java.util.concurrent包中,涉及到的幾個(gè)核心類(lèi)及接口包括:
Executor、Executors、ExecutorService、ThreadPoolExecutor、FutureTask、Callable、Runnable等。
線(xiàn)程池可以自動(dòng)創(chuàng )建也可以手動(dòng)創(chuàng )建,自動(dòng)創(chuàng )建體現在Executors工具類(lèi)中,常見(jiàn)的可以創(chuàng )建newFixedThreadPool、newCachedThreadPool、newSingleThreadExecutor、newScheduledThreadPool;
手動(dòng)創(chuàng )建體現在可以靈活設置線(xiàn)程池的各個(gè)參數,體現在代碼中即ThreadPoolExecutor類(lèi)構造器上各個(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) {……}
線(xiàn)程池中的線(xiàn)程創(chuàng )建流程圖:
(基于<Java并發(fā)編程的藝術(shù)>一書(shū))
舉個(gè)例子:
現有一個(gè)線(xiàn)程池,corePoolSize=10,maxPoolSize=20,隊列長(cháng)度為100,那么當任務(wù)過(guò)來(lái)會(huì )先創(chuàng )建10個(gè)核心線(xiàn)程數,接下來(lái)進(jìn)來(lái)的任務(wù)會(huì )進(jìn)入到隊列中直到隊列滿(mǎn)了,會(huì )創(chuàng )建額外的線(xiàn)程來(lái)執行任務(wù)(最多20個(gè)線(xiàn)程),這個(gè)時(shí)候如果再來(lái)任務(wù)就會(huì )執行拒絕策略。
SynchronousQueue(同步移交隊列):隊列不作為任務(wù)的緩沖方式,可以簡(jiǎn)單理解為隊列長(cháng)度為零LinkedBlockingQueue(無(wú)界隊列):隊列長(cháng)度不受限制,當請求越來(lái)越多時(shí)(任務(wù)處理速度跟不上任務(wù)處理速度造成請求堆積)可能導致內存占用過(guò)多或OOMArrayBlockintQueue(有界隊列):隊列長(cháng)度受限,當隊列滿(mǎn)了就需要創(chuàng )建多余的線(xiàn)程來(lái)執行任務(wù)
自動(dòng)創(chuàng )建線(xiàn)程池的幾種方式都封裝在Executors工具類(lèi)中:
newFixedThreadPool:使用的構造方式為new ThreadPoolExecutor(var0, var0, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()),設置了corePoolSize=maxPoolSize,keepAliveTime=0(此時(shí)該參數沒(méi)作用),無(wú)界隊列,任務(wù)可以無(wú)限放入,當請求過(guò)多時(shí)(任務(wù)處理速度跟不上任務(wù)提交速度造成請求堆積)可能導致占用過(guò)多內存或直接導致OOM異常
newSingleThreadExector:使用的構造方式為new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(), var0),基本同newFixedThreadPool,但是將線(xiàn)程數設置為了1,單線(xiàn)程,弊端和newFixedThreadPool一致
newCachedThreadPool: 使用的構造方式為new ThreadPoolExecutor(0, 2147483647, 60L, TimeUnit.SECONDS, new SynchronousQueue()),corePoolSize=0,maxPoolSize為很大的數,同步移交隊列,也就是說(shuō)不維護常駐線(xiàn)程(核心線(xiàn)程),每次來(lái)請求直接創(chuàng )建新線(xiàn)程來(lái)處理任務(wù),也不使用隊列緩沖,會(huì )自動(dòng)回收多余線(xiàn)程,由于將maxPoolSize設置成Integer.MAX_VALUE,當請求很多時(shí)就可能創(chuàng )建過(guò)多的線(xiàn)程,導致資源耗盡OOM
newScheduledThreadPool:使用的構造方式為new ThreadPoolExecutor(var1, 2147483647, 0L, TimeUnit.NANOSECONDS, new ScheduledThreadPoolExecutor.DelayedWorkQueue()),支持定時(shí)周期性執行,注意一下使用的是延遲隊列,弊端同newCachedThreadPool一致
所以根據上面分析我們可以看到,FixedThreadPool和SigleThreadExecutor中之所以用LinkedBlockingQueue無(wú)界隊列,是因為設置了corePoolSize=maxPoolSize,線(xiàn)程數無(wú)法動(dòng)態(tài)擴展,于是就設置了無(wú)界阻塞隊列來(lái)應對不可知的任務(wù)量;而CachedThreadPool則使用的是SynchronousQueue同步移交隊列,為什么使用這個(gè)隊列呢?
因為CachedThreadPool設置了corePoolSize=0,maxPoolSize=Integer.MAX_VALUE,來(lái)一個(gè)任務(wù)就創(chuàng )建一個(gè)線(xiàn)程來(lái)執行任務(wù),用不到隊列來(lái)存儲任務(wù);SchduledThreadPool用的是延遲隊列DelayedWorkQueue。在實(shí)際項目開(kāi)發(fā)中也是推薦使用手動(dòng)創(chuàng )建線(xiàn)程池的方式,而不用默認方式。
關(guān)于這點(diǎn)在《阿里巴巴開(kāi)發(fā)規范》中是這樣描述的:
handler拒絕策略
關(guān)閉線(xiàn)程池
1.線(xiàn)程池里執行的是任務(wù),核心邏輯在ThreadPoolExecutor類(lèi)的execute方法中,同時(shí)ThreadPoolExecutor中維護了HashSet<Worker> workers;
2.addWorker()方法來(lái)創(chuàng )建線(xiàn)程執行任務(wù),如果是核心線(xiàn)程的任務(wù),會(huì )賦值給Worker的firstTask屬性;
3.Worker實(shí)現了Runnable,本質(zhì)上也是任務(wù),核心在run()方法里;
4.run()方法的執行核心runWorker(),自旋拿任務(wù)while (task != null || (task = getTask()) != null)),task是核心線(xiàn)程Worker的firstTask或者getTask();
5.getTask()的核心邏輯:
1.若當前工作線(xiàn)程數量大于核心線(xiàn)程數->說(shuō)明此線(xiàn)程是非核心工作線(xiàn)程,通過(guò)poll()拿任務(wù),未拿到任務(wù)即getTask()返回null,然后會(huì )在processWorkerExit(w, completedAbruptly)方法釋放掉這個(gè)非核心工作線(xiàn)程的引用;
2.若當前工作線(xiàn)程數量小于核心線(xiàn)程數->說(shuō)明此時(shí)線(xiàn)程是核心工作線(xiàn)程,通過(guò)take()拿任務(wù)
3.take()方式取任務(wù),如果隊列中沒(méi)有任務(wù)了會(huì )調用await()阻塞當前線(xiàn)程,直到新任務(wù)到來(lái),所以核心工作線(xiàn)程不會(huì )被回收; 當執行execute方法里的workQueue.offer(command)時(shí)會(huì )調用Condition.singal()方法喚醒一個(gè)之前阻塞的線(xiàn)程,這樣核心線(xiàn)程即可復用
那么上面說(shuō)了使用Executors工具類(lèi)創(chuàng )建的線(xiàn)程池有隱患,那如何使用才能避免這個(gè)隱患呢?對癥下藥,建立自己的線(xiàn)程工廠(chǎng)類(lèi),靈活設置關(guān)鍵參數:
//這里默認拒絕策略為AbortPolicy private static ExecutorService executor = new ThreadPoolExecutor(10,10,60L, TimeUnit.SECONDS,new ArrayBlockingQueue(10));
使用guava包中的ThreadFactoryBuilder工廠(chǎng)類(lèi)來(lái)構造線(xiàn)程池:
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工廠(chǎng)類(lèi)還可以指定線(xiàn)程組名稱(chēng),這對于后期定位錯誤時(shí)也是很有幫助的
ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("thread-pool-d%").build();
springboot可以說(shuō)是非常流行了,下面說(shuō)說(shuō)如何在springboot中優(yōu)雅的使用線(xiàn)程池
/** * @ClassName ThreadPoolConfig * @Description 配置類(lèi)中構建線(xiàn)程池實(shí)例,方便調用 * @Author ww * @Date 2021/5/11 * Version 1.0 */ @Configuration public class ThreadPoolConfig { @Bean(value = "threadPoolInstance") public ExecutorService createThreadPoolInstance() { //通過(guò)guava類(lèi)庫的ThreadFactoryBuilder來(lái)實(shí)現線(xiàn)程工廠(chǎng)類(lèi)并設置線(xiàn)程名稱(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)中構建線(xiàn)程池實(shí)例,方便調用 * @Author ww * @Date 2021/5/11 * Version 1.0 */ @Configuration public class ThreadPoolConfig { @Bean(value = "threadPoolInstance") public ExecutorService createThreadPoolInstance() { //通過(guò)guava類(lèi)庫的ThreadFactoryBuilder來(lái)實(shí)現線(xiàn)程工廠(chǎng)類(lèi)并設置線(xiàn)程名稱(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í)現自己的業(yè)務(wù)邏輯,非常適合做鉤子函數,在任務(wù)run方法的前后增加業(yè)務(wù)邏輯,比如添加日志、統計等。
這個(gè)和我們springmvc中攔截器的preHandle和afterCompletion方法很類(lèi)似,都是對方法進(jìn)行環(huán)繞,類(lèi)似于spring的AOP。
到此這篇關(guān)于淺談Java線(xiàn)程池的7大核心參數的文章就介紹到這了,更多相關(guān)Java線(xiàn)程池核心參數內容請搜索腳本之家以前的文章或繼續瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng )、來(lái)自本網(wǎng)站內容采集于網(wǎng)絡(luò )互聯(lián)網(wǎng)轉載等其它媒體和分享為主,內容觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如侵犯了原作者的版權,請告知一經(jīng)查實(shí),將立刻刪除涉嫌侵權內容,聯(lián)系我們QQ:712375056,同時(shí)歡迎投稿傳遞力量。
Copyright ? 2009-2022 56dr.com. All Rights Reserved. 特網(wǎng)科技 特網(wǎng)云 版權所有 特網(wǎng)科技 粵ICP備16109289號
域名注冊服務(wù)機構:阿里云計算有限公司(萬(wàn)網(wǎng)) 域名服務(wù)機構:煙臺帝思普網(wǎng)絡(luò )科技有限公司(DNSPod) CDN服務(wù):阿里云計算有限公司 百度云 中國互聯(lián)網(wǎng)舉報中心 增值電信業(yè)務(wù)經(jīng)營(yíng)許可證B2
建議您使用Chrome、Firefox、Edge、IE10及以上版本和360等主流瀏覽器瀏覽本網(wǎng)站