Java的ThreadPoolExecutor業(yè)務線程池詳細解析
ThreadPoolExecutor業(yè)務線程池
1.什么是業(yè)務線程池?
在業(yè)務開發(fā)中,用來處理業(yè)務的線程池。
2.為什么需要業(yè)務線程池?
大多數(shù)同學都是做業(yè)務開發(fā)的,很多業(yè)務的操作并非要求一定是同步的。例如,對于一系列連續(xù)的業(yè)務邏輯處理,很多都是數(shù)據(jù)的組裝,拼接,查詢,或者將數(shù)據(jù)同步給各個下層業(yè)務(對事務性沒有嚴格要求);或者對數(shù)據(jù)的批量操作;這些都可以是異步的。通常業(yè)務項目使用的都是的servlet框架,都是使用一個線程進行業(yè)務邏輯處理,這種模型是通用的,但不一定是最佳的,不一定是最適合的。需要我們業(yè)務開發(fā)者根據(jù)實際的業(yè)務場景去靈活應用,達到最快的響應,最大的吞吐量。
3.業(yè)務線程池應用的思路是來自哪里?
個人理解,來自于開源框架。各種池化的概念,太多了,線程池,內存池,實例池,連接池。太多框架使用了線程池的概念,spring,tomcat,dubbo,netty,rocketmq,nacos,druid,總而言之,幾乎所有的框架,都用到了線程池。雖然他們是框架線程池,但是抽出來想一下,對于框架線程池來講,我們對于框架的使用,也是業(yè)務流程,也需要業(yè)務邏輯的處理,因此,業(yè)務線程池,框架線程池,兩者并無區(qū)別。
一、業(yè)務線程池的好處
這里借用《Java 并發(fā)編程的藝術》提到的來說一下使用線程池的好處:
- 降低資源消耗。通過重復利用已創(chuàng)建的線程降低線程創(chuàng)建和銷毀造成的消耗。
- 提高響應速度。當任務到達時,任務可以不需要的等到線程創(chuàng)建就能立即執(zhí)行。
- 提高線程的可管理性。線程是稀缺資源,如果無限制的創(chuàng)建,不僅會消耗系統(tǒng)資源,還會降低系統(tǒng)的穩(wěn)定性,使用線程池可以進行統(tǒng)一的分配,調優(yōu)和監(jiān)控。
二、線程池基本認識
參數(shù)說明
/**
* 用給定的初始參數(shù)創(chuàng)建一個新的ThreadPoolExecutor。
*/
public ThreadPoolExecutor(int corePoolSize,//線程池的核心線程數(shù)量
int maximumPoolSize,//線程池的最大線程數(shù)
long keepAliveTime,//當線程數(shù)大于核心線程數(shù)時,多余的空閑線程存活的最長時間
TimeUnit unit,//時間單位
BlockingQueue<Runnable> workQueue,//任務隊列,用來儲存等待執(zhí)行任務的隊列
ThreadFactory threadFactory,//線程工廠,用來創(chuàng)建線程,一般默認即可
RejectedExecutionHandler handler//拒絕策略,當提交的任務過多而不能及時處理時,我們可以定制策略來處理任務
) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}拒絕策略
- AbortPolicy:直接拋出異常,這是默認策略;
- CallerRunsPolicy:用調用者所在的線程來執(zhí)行任務;(這種場景下,可以保證數(shù)據(jù)不丟失,但是會阻塞主線程)
- DiscardOldestPolicy:丟棄阻塞隊列中靠最前的任務,并執(zhí)行當前任務;
- DiscardPolicy:直接丟棄任務;
ExecutorService 中 shutdown()、shutdownNow()、awaitTermination() 含義和區(qū)別
- shutdown():停止接收新任務,原來的任務繼續(xù)執(zhí)行
- shutdownNow():停止接收新任務,原來的任務停止執(zhí)行
- awaitTermination(long timeOut, TimeUnit unit):當前線程阻塞
注意:
awaitTermination一般是配合shutdown使用。
ThreadPoolExecutor運行狀態(tài)
ThreadPoolExecutor類中定義了5個Integer常量,狀態(tài)分別為
// runState is stored in the high-order bits
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;

經(jīng)典面試題
線程池什么時候創(chuàng)建核心線程,什么時候把任務放進阻塞隊列,什么時候創(chuàng)建空閑線程?
答:任務剛開始進來的時候就創(chuàng)建核心線程,核心線程滿了會把任務放到阻塞隊列,阻塞隊列滿了之后才會創(chuàng)建空閑線程,達到最大線程數(shù)之后,再有任務進來,就只能執(zhí)行拒絕策略了。
注意,執(zhí)行拒絕策略有兩個場景,一個是空閑線程也滿了,二是線程池不在運行了,比如執(zhí)行了shutdown的方法,但是這個時候又來了新任務。

基礎知識
現(xiàn)阻塞隊列的接口是BlockingQueue,jdk1.5新增的,在juc包下面,作者是Doug Lea,它的父接口是Queue,也是jdk1.5新增的,在java.util包下面,屬于集合類,作者還是Doug Lea。
三、線程池最佳實踐
1.打印線程池的狀態(tài),關注線程池運行情況(個人非常喜歡)
/**
* 打印線程池的狀態(tài)
*
* @param threadPool 線程池對象
*/
public static void printThreadPoolStatus(ThreadPoolExecutor threadPool) {
ScheduledExecutorService scheduledExecutorService = new ScheduledThreadPoolExecutor(1, createThreadFactory("print-thread-pool-status", false));
scheduledExecutorService.scheduleAtFixedRate(() -> {
log.info("=========================");
log.info("ThreadPool Size: [{}]", threadPool.getPoolSize());
log.info("Active Threads: {}", threadPool.getActiveCount());
log.info("Number of Tasks : {}", threadPool.getCompletedTaskCount());
log.info("Number of Tasks in Queue: {}", threadPool.getQueue().size());
log.info("=========================");
}, 0, 1, TimeUnit.SECONDS);
}2.不同業(yè)務使用不同的業(yè)務線程池
父子任務也不要使用一個線程池(會發(fā)生死鎖),死鎖原因:父任務占用了所有的核心線程,自子任務在阻塞隊列里等待父任務釋放核心線程,父線程等待子任務完成任務。
3.為什么不能使用原生的Executors工具創(chuàng)建線程池
阻塞隊列都是Integer.MAX,容易發(fā)生OOM,而且無線程池命名,沒有關心空閑時間,拒絕策略,太粗糙了,除非你不關心業(yè)務。
4.如果設置線程數(shù)量?
有一個簡單并且適用面比較廣的公式:
- CPU 密集型任務(N+1): 這種任務消耗的主要是 CPU 資源,可以將線程數(shù)設置為 N(CPU 核心數(shù))+1,比 CPU 核心數(shù)多出來的一個線程是為了防止線程偶發(fā)的缺頁中斷,或者其它原因導致的任務暫停而帶來的影響。一旦任務暫停,CPU 就會處于空閑狀態(tài),而在這種情況下多出來的一個線程就可以充分利用 CPU 的空閑時間。
- I/O 密集型任務(2N): 這種任務應用起來,系統(tǒng)會用大部分的時間來處理 I/O 交互,而線程在處理 I/O 的時間段內不會占用 CPU 來處理,這時就可以將 CPU 交出給其它線程使用。因此在 I/O 密集型任務的應用中,我們可以多配置一些線程,具體的計算方法是 2N。
5.[美團] Java線程池實現(xiàn)原理及其在美團業(yè)務中的實踐
由于隊列設置過長,最大線程數(shù)設置失效,導致請求數(shù)量增加時,大量任務堆積在隊列中,任務執(zhí)行時間過長,最終導致下游服務的大量調用超時失敗。
ThreadPoolExecutor的corePoolSize的值是可以設置的。利用這點加上配置中心,可以動態(tài)的調整核心線程數(shù)。
四、線程池總結
做好業(yè)務線程池,分三個級別
第一級別,根據(jù)業(yè)務特性實現(xiàn)不同的業(yè)務線程池。
第二級別,根據(jù)業(yè)務特性,動態(tài)調整線程池配置。
第三級別,實時監(jiān)控與配置線程池運行情況。
到此這篇關于Java的ThreadPoolExecutor業(yè)務線程池詳細解析的文章就介紹到這了,更多相關ThreadPoolExecutor業(yè)務線程池內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
ZooKeeper框架教程Curator分布式鎖實現(xiàn)及源碼分析
本文是ZooKeeper入門系列教程,本篇為大家介紹zookeeper一個優(yōu)秀的框架Curator,提供了各種分布式協(xié)調的服務,Curator中有著更為標準、規(guī)范的分布式鎖實現(xiàn)2022-01-01
Java+swing實現(xiàn)經(jīng)典貪吃蛇游戲
貪吃蛇(也叫做貪食蛇)游戲是一款休閑益智類游戲,有PC和手機等多平臺版本。既簡單又耐玩。本文將通過java的swing來實現(xiàn)這一游戲,需要的可以參考一下2022-01-01
java父子線程之間實現(xiàn)共享傳遞數(shù)據(jù)
本文介紹了Java中父子線程間共享傳遞數(shù)據(jù)的幾種方法,包括ThreadLocal變量、并發(fā)集合和內存隊列或消息隊列,并提醒注意并發(fā)安全問題2025-02-02

