簡單聊一聊Java線程池ThreadPoolExecutor
簡介
ThreadPoolExecutor
是一個實現(xiàn)ExecutorService
接口的線程池,ExecutorService
是主要用來處理多線程任務(wù)的一個接口,通常比較簡單是用法是由Executors
工廠類去創(chuàng)建。
線程池主要解決了兩個不同的問題:
- 在執(zhí)行大量異步任務(wù)時,為了能夠提高性能,通常會減少每個任務(wù)的調(diào)用開銷。
- 提供了一系列多線程任務(wù)的管理方法,便于多任務(wù)執(zhí)行時合理分配資源以及一些異常情況的處理。每個ThreadPoolExecutor還維護一些基本統(tǒng)計信息。例如:已完成任務(wù)的數(shù)量,當前獲得線程數(shù)等。
參數(shù)說明
ThreadPoolExecutor
提供了幾個核心參數(shù),方便開發(fā)人員根據(jù)具體場景合理分配線程資源。
corePoolSize
:核心線程數(shù),在線程池創(chuàng)建時就已初始化好的n個核心線程,即使線程空閑著也會一直保留在線程池中不被銷毀,除非調(diào)用線程池方法設(shè)置了java.util.concurrent.ThreadPoolExecutor#allowCoreThreadTimeOut(true)
(允許核心線程超時銷毀)。maximumPoolSize
:線程池允許存在最大線程數(shù)。keepAliveTime
:當線程數(shù)大于核心線程數(shù)時,多余的線程在執(zhí)行任務(wù)結(jié)束后等待新任務(wù)的最大等待時間。unit
:TimeUnit
類型,是keepAliveTime
多余線程最大空余時間單位。workQueue
:必須指定一個阻塞隊列,在線程池執(zhí)行execute
方法時新進來的任務(wù)在執(zhí)行前都會保留到此隊列里進入等待。threadFactory
:創(chuàng)建線程的工廠,默認采用Executors.defaultThreadFactory()
創(chuàng)建線程。handler
:拒絕策略,當最大線程數(shù)已占滿,且隊列已滿,此時線程池將觸發(fā)拒絕策略,對新進來的任務(wù)做拒絕處理,具體的處理方案在后面詳細分析(默認使用java.util.concurrent.ThreadPoolExecutor.AbortPolicy
直接拋出異常拒絕處理)。
注:
maximumPoolSize
如果大于corePoolSize
,則多出的部分線程數(shù)只有在阻塞隊列workQueue占滿時才會創(chuàng)建核心線程之外的線程去執(zhí)行任務(wù),如果我們設(shè)置的阻塞隊列為無界隊列(默認大小為Integer.MAX_VALUE
),則隊列永遠無法占滿,就不會去創(chuàng)建額外的線程進行工作,一般情況如果任務(wù)數(shù)足夠,那么也是在隊列大小還沒達到Integer.MAX_VALUE
時就已經(jīng)出現(xiàn)內(nèi)存溢出了。Executors
線程池工廠中的newFixedThreadPool()、newSingleThreadExecutor()
方法就是使用了無界隊列LinkedBlockingQueue
,防止內(nèi)存溢出在日常開發(fā)過程中一般是不建議直接去使用Executors
去創(chuàng)建線程池。
如何創(chuàng)建線程池
上面我們提到的可以使用Executors
工廠直接創(chuàng)建線程池,但是Executors
提供的創(chuàng)建線程池都是不可控的,我們還是得按自己的業(yè)務(wù)做好分析自定義一個線程池。
以下是線程池創(chuàng)建的一個案例:
@Slf4j @Configuration public class ThreadPoolConfig { @Value("${threadPool.corePoolSize:8}") private int corePoolSize; @Value("${threadPool.maximumPoolSize:16}") private int maximumPoolSize; @Value("${threadPool.keepAliveTime:60}") private int keepAliveTime; @Value("${threadPool.queueSize:99999}") private int queueSize; @Bean public ThreadPoolExecutor testExecutor() { LinkedBlockingQueue queue = new LinkedBlockingQueue(queueSize); return new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.SECONDS, queue, getThreadFactory(), getRejectedExecutionHandler()); } /** * 自定義線程池創(chuàng)建線程工廠,用于線程池創(chuàng)建線程的工廠 * @return */ private ThreadFactory getThreadFactory() { return new ThreadFactory() { @Override public Thread newThread(Runnable r) { log.info("===> Create new thread ..."); return new Thread(r); } }; } /** * 自定義拒絕策略,繼續(xù)往隊列里添加任務(wù)進入等待 * @return */ private RejectedExecutionHandler getRejectedExecutionHandler() { return new RejectedExecutionHandler() { @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { // 繼續(xù)往隊列里添加任務(wù),這里只是一個案例,這種方式并不友好,會拋出隊列已滿的異常 log.info("===> Handler runnable ......"); executor.getQueue().add(r); } }; } }
application.properties
配置文件
threadPool: corePoolSize: 8 maximumPoolSize: 16 keepAliveTime: 60 # 為方便測試這里我們配置隊列數(shù)小一點 queueSize: 99
由以上的線程池配置,我們寫一個demo測試一下:
截取部分運行日志:
- 紅框我們可以看到執(zhí)行到了線程創(chuàng)建工廠部分代碼塊
- 藍色框日志我們可以看到
largestPoolSize=11
,這是由于我們配置的maximumPoolSize=16 > corePoolSize=8
,我們demo執(zhí)行的是110個任務(wù)并發(fā),隊列大小是99,由此分析得出(需要額外出創(chuàng)建線程數(shù) = 并發(fā)任務(wù)總數(shù)110 - 核心線程數(shù)8 - 隊列大小99 = 3),所以線程池在隊列已滿時會多創(chuàng)建3個線程用于執(zhí)行任務(wù),在達到keepAliveTime
配置的最大空閑時間后這3個線程即會自動銷毀。
注:可能有的同學會想線程池使用后需要銷毀嗎?在這里補充一下,如果我們是作為局部變量創(chuàng)建出來的線程池(如:在執(zhí)行的方法內(nèi)使用
Executors.newFixedThreadPool(10)
創(chuàng)建臨時的線程池),這種情況我們用完就必須將它立即銷毀,否則主線程就會一直處于運行狀態(tài)。如果是全局配置的線程池,那么就是為整個系統(tǒng)中諸多業(yè)務(wù)提供使用的,這種就不需要對線程池做銷毀,因為一旦銷毀了其他的任務(wù)就無法繼續(xù)使用該線程池執(zhí)行任務(wù)。
- 銷毀線程池主要有兩種方式:
shutdown()
:此方法對線程池做銷毀,線程池會優(yōu)先將剩余未完成的任務(wù)執(zhí)行完才會執(zhí)行銷毀任務(wù)。shutdownNow()
:此方法會對線程池做立即銷毀,無論線程池中的任務(wù)是否執(zhí)行完成。
拒絕策略
通常我們在配置好有限隊列大小后,就會有可能出現(xiàn)隊列占滿的情況,這時候我們的拒絕策略就會起到作用,接下來我們就來分析一下RejectedExecutionHandler
接口具體有哪一些實現(xiàn)方式:
- AbortPolicy:線程池的默認拒絕策略,在JDK提供的
ThreadPoolExecutor
線程池中有一個默認線程池變量private static final RejectedExecutionHandler defaultHandler = new AbortPolicy();
作為默認拒絕策略,查看如下圖源碼可知它就是直接拋出RejectedExecutionException
異常,并且會直接丟棄當前任務(wù),如果擔心異常影響后續(xù)任務(wù)執(zhí)行開發(fā)人員需自行捕獲異常處理。
- CallerRunsPolicy:只要在當線程池未被銷毀的情況下,不丟棄任務(wù)直接使用主線程(調(diào)用線程池執(zhí)行的線程)執(zhí)行該任務(wù)。因為該策略是由主線程直接執(zhí)行任務(wù)的,所以不建議在并發(fā)度高的情況下使用,建議在并發(fā)度較低且任務(wù)不允許失敗的情況下才使用此策略。
- DiscardPolicy:直接丟棄當前任務(wù),不做任何處理。直接丟棄任務(wù)的情況下,開發(fā)人員也無法排查到哪些任務(wù)被丟棄掉,一般不建議使用,除非是無關(guān)緊要的任務(wù)即使丟棄也無所謂的。
- DiscardOldestPolicy:在線程池未被銷毀的情況下,丟棄最早進入隊列的一個任務(wù)(即最久未執(zhí)行的任務(wù)),然后再重新將此任務(wù)加入線程池,在此策略下需注意被丟棄的任務(wù)的重要性,如果任務(wù)不重要可直接丟棄。
- 自定義策略:在以上JDK提供的四種默認拒絕策略之外,我們還可以通過自定義的方式來處理被拒絕的任務(wù)。如果擔心任務(wù)被拒絕或者被丟棄造成不可預(yù)估的問題,在時效性沒有太大要求的情況下我們可以先將任務(wù)內(nèi)容轉(zhuǎn)換成數(shù)據(jù)入庫做好日志記錄,后續(xù)可以使用定時任務(wù)或者通過MQ消息延遲處理。由以上的線程池配置Demo中的拒絕策略改造偽代碼如下:
private RejectedExecutionHandler getRejectedExecutionHandler() { return new RejectedExecutionHandler() { @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { // 偽代碼 log.info("===> 可根據(jù)任務(wù)的重要性區(qū)分對待,將任務(wù)做轉(zhuǎn)換入庫延遲處理 ......"); } }; }
總結(jié)
線程池是為了充分利用CPU資源,在合理分批使用的情況下能夠極大的提高我們程序的性能,以上的參數(shù)配置僅作為參考,并沒有一個標準的依據(jù),只能在實際開發(fā)過程中開發(fā)人員自行多做一些測試來判斷參數(shù)如何配置更加合理。
在拒絕策略配置方面,如果被拒絕的任務(wù)相對緊急且重要不可丟棄的情況下,此類任務(wù)可獨立做一個線程池處理保證任務(wù)不丟失,程序只能慢慢優(yōu)化變得越來越好,不可能有完美的程序即保證高性能又保證安全可靠。
到此這篇關(guān)于Java線程池ThreadPoolExecutor的文章就介紹到這了,更多相關(guān)Java線程池ThreadPoolExecutor內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Java ThreadPoolExecutor 線程池的使用介紹
- java 定時器線程池(ScheduledThreadPoolExecutor)的實現(xiàn)
- java多線程CountDownLatch與線程池ThreadPoolExecutor/ExecutorService案例
- Java線程池ThreadPoolExecutor原理及使用實例
- java線程池對象ThreadPoolExecutor的深入講解
- java線程池ThreadPoolExecutor的八種拒絕策略示例詳解
- 詳解Java并發(fā)包中線程池ThreadPoolExecutor
- Java并發(fā)包線程池ThreadPoolExecutor的實現(xiàn)
- Java創(chuàng)建線程池為什么一定要用ThreadPoolExecutor
相關(guān)文章
springmvc+shiro+maven 實現(xiàn)登錄認證與權(quán)限授權(quán)管理
Shiro 是一個 Apache 下的一開源項目項目,旨在簡化身份驗證和授權(quán),下面通過實例代碼給大家分享springmvc+shiro+maven 實現(xiàn)登錄認證與權(quán)限授權(quán)管理,感興趣的朋友一起看看吧2017-09-09解決Springboot項目打包后的頁面丟失問題(thymeleaf報錯)
這篇文章主要介紹了解決Springboot項目打包后的頁面丟失問題(thymeleaf報錯),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-11-11SpringBoot2.x 集成 Thymeleaf的詳細教程
本文主要對SpringBoot2.x集成Thymeleaf及其常用語法進行簡單總結(jié),其中SpringBoot使用的2.4.5版本。對SpringBoot2.x 集成 Thymeleaf知識感興趣的朋友跟隨小編一起看看吧2021-07-07