Java的ThreadPoolExecutor業(yè)務(wù)線程池詳細(xì)解析
ThreadPoolExecutor業(yè)務(wù)線程池
1.什么是業(yè)務(wù)線程池?
在業(yè)務(wù)開(kāi)發(fā)中,用來(lái)處理業(yè)務(wù)的線程池。
2.為什么需要業(yè)務(wù)線程池?
大多數(shù)同學(xué)都是做業(yè)務(wù)開(kāi)發(fā)的,很多業(yè)務(wù)的操作并非要求一定是同步的。例如,對(duì)于一系列連續(xù)的業(yè)務(wù)邏輯處理,很多都是數(shù)據(jù)的組裝,拼接,查詢,或者將數(shù)據(jù)同步給各個(gè)下層業(yè)務(wù)(對(duì)事務(wù)性沒(méi)有嚴(yán)格要求);或者對(duì)數(shù)據(jù)的批量操作;這些都可以是異步的。通常業(yè)務(wù)項(xiàng)目使用的都是的servlet框架,都是使用一個(gè)線程進(jìn)行業(yè)務(wù)邏輯處理,這種模型是通用的,但不一定是最佳的,不一定是最適合的。需要我們業(yè)務(wù)開(kāi)發(fā)者根據(jù)實(shí)際的業(yè)務(wù)場(chǎng)景去靈活應(yīng)用,達(dá)到最快的響應(yīng),最大的吞吐量。
3.業(yè)務(wù)線程池應(yīng)用的思路是來(lái)自哪里?
個(gè)人理解,來(lái)自于開(kāi)源框架。各種池化的概念,太多了,線程池,內(nèi)存池,實(shí)例池,連接池。太多框架使用了線程池的概念,spring,tomcat,dubbo,netty,rocketmq,nacos,druid,總而言之,幾乎所有的框架,都用到了線程池。雖然他們是框架線程池,但是抽出來(lái)想一下,對(duì)于框架線程池來(lái)講,我們對(duì)于框架的使用,也是業(yè)務(wù)流程,也需要業(yè)務(wù)邏輯的處理,因此,業(yè)務(wù)線程池,框架線程池,兩者并無(wú)區(qū)別。
一、業(yè)務(wù)線程池的好處
這里借用《Java 并發(fā)編程的藝術(shù)》提到的來(lái)說(shuō)一下使用線程池的好處:
- 降低資源消耗。通過(guò)重復(fù)利用已創(chuàng)建的線程降低線程創(chuàng)建和銷毀造成的消耗。
- 提高響應(yīng)速度。當(dāng)任務(wù)到達(dá)時(shí),任務(wù)可以不需要的等到線程創(chuàng)建就能立即執(zhí)行。
- 提高線程的可管理性。線程是稀缺資源,如果無(wú)限制的創(chuàng)建,不僅會(huì)消耗系統(tǒng)資源,還會(huì)降低系統(tǒng)的穩(wěn)定性,使用線程池可以進(jìn)行統(tǒng)一的分配,調(diào)優(yōu)和監(jiān)控。
二、線程池基本認(rèn)識(shí)
參數(shù)說(shuō)明
/** * 用給定的初始參數(shù)創(chuàng)建一個(gè)新的ThreadPoolExecutor。 */ public ThreadPoolExecutor(int corePoolSize,//線程池的核心線程數(shù)量 int maximumPoolSize,//線程池的最大線程數(shù) long keepAliveTime,//當(dāng)線程數(shù)大于核心線程數(shù)時(shí),多余的空閑線程存活的最長(zhǎng)時(shí)間 TimeUnit unit,//時(shí)間單位 BlockingQueue<Runnable> workQueue,//任務(wù)隊(duì)列,用來(lái)儲(chǔ)存等待執(zhí)行任務(wù)的隊(duì)列 ThreadFactory threadFactory,//線程工廠,用來(lái)創(chuàng)建線程,一般默認(rèn)即可 RejectedExecutionHandler handler//拒絕策略,當(dāng)提交的任務(wù)過(guò)多而不能及時(shí)處理時(shí),我們可以定制策略來(lái)處理任務(wù) ) { 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:直接拋出異常,這是默認(rèn)策略;
- CallerRunsPolicy:用調(diào)用者所在的線程來(lái)執(zhí)行任務(wù);(這種場(chǎng)景下,可以保證數(shù)據(jù)不丟失,但是會(huì)阻塞主線程)
- DiscardOldestPolicy:丟棄阻塞隊(duì)列中靠最前的任務(wù),并執(zhí)行當(dāng)前任務(wù);
- DiscardPolicy:直接丟棄任務(wù);
ExecutorService 中 shutdown()、shutdownNow()、awaitTermination() 含義和區(qū)別
- shutdown():停止接收新任務(wù),原來(lái)的任務(wù)繼續(xù)執(zhí)行
- shutdownNow():停止接收新任務(wù),原來(lái)的任務(wù)停止執(zhí)行
- awaitTermination(long timeOut, TimeUnit unit):當(dāng)前線程阻塞
注意:
awaitTermination一般是配合shutdown使用。
ThreadPoolExecutor運(yùn)行狀態(tài)
ThreadPoolExecutor類中定義了5個(gè)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)典面試題
線程池什么時(shí)候創(chuàng)建核心線程,什么時(shí)候把任務(wù)放進(jìn)阻塞隊(duì)列,什么時(shí)候創(chuàng)建空閑線程?
答:任務(wù)剛開(kāi)始進(jìn)來(lái)的時(shí)候就創(chuàng)建核心線程,核心線程滿了會(huì)把任務(wù)放到阻塞隊(duì)列,阻塞隊(duì)列滿了之后才會(huì)創(chuàng)建空閑線程,達(dá)到最大線程數(shù)之后,再有任務(wù)進(jìn)來(lái),就只能執(zhí)行拒絕策略了。
注意,執(zhí)行拒絕策略有兩個(gè)場(chǎng)景,一個(gè)是空閑線程也滿了,二是線程池不在運(yùn)行了,比如執(zhí)行了shutdown的方法,但是這個(gè)時(shí)候又來(lái)了新任務(wù)。
基礎(chǔ)知識(shí)
現(xiàn)阻塞隊(duì)列的接口是BlockingQueue,jdk1.5新增的,在juc包下面,作者是Doug Lea,它的父接口是Queue,也是jdk1.5新增的,在java.util包下面,屬于集合類,作者還是Doug Lea。
三、線程池最佳實(shí)踐
1.打印線程池的狀態(tài),關(guān)注線程池運(yùn)行情況(個(gè)人非常喜歡)
/** * 打印線程池的狀態(tài) * * @param threadPool 線程池對(duì)象 */ 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è)務(wù)使用不同的業(yè)務(wù)線程池
父子任務(wù)也不要使用一個(gè)線程池(會(huì)發(fā)生死鎖),死鎖原因:父任務(wù)占用了所有的核心線程,自子任務(wù)在阻塞隊(duì)列里等待父任務(wù)釋放核心線程,父線程等待子任務(wù)完成任務(wù)。
3.為什么不能使用原生的Executors工具創(chuàng)建線程池
阻塞隊(duì)列都是Integer.MAX,容易發(fā)生OOM,而且無(wú)線程池命名,沒(méi)有關(guān)心空閑時(shí)間,拒絕策略,太粗糙了,除非你不關(guān)心業(yè)務(wù)。
4.如果設(shè)置線程數(shù)量?
有一個(gè)簡(jiǎn)單并且適用面比較廣的公式:
- CPU 密集型任務(wù)(N+1): 這種任務(wù)消耗的主要是 CPU 資源,可以將線程數(shù)設(shè)置為 N(CPU 核心數(shù))+1,比 CPU 核心數(shù)多出來(lái)的一個(gè)線程是為了防止線程偶發(fā)的缺頁(yè)中斷,或者其它原因?qū)е碌娜蝿?wù)暫停而帶來(lái)的影響。一旦任務(wù)暫停,CPU 就會(huì)處于空閑狀態(tài),而在這種情況下多出來(lái)的一個(gè)線程就可以充分利用 CPU 的空閑時(shí)間。
- I/O 密集型任務(wù)(2N): 這種任務(wù)應(yīng)用起來(lái),系統(tǒng)會(huì)用大部分的時(shí)間來(lái)處理 I/O 交互,而線程在處理 I/O 的時(shí)間段內(nèi)不會(huì)占用 CPU 來(lái)處理,這時(shí)就可以將 CPU 交出給其它線程使用。因此在 I/O 密集型任務(wù)的應(yīng)用中,我們可以多配置一些線程,具體的計(jì)算方法是 2N。
5.[美團(tuán)] Java線程池實(shí)現(xiàn)原理及其在美團(tuán)業(yè)務(wù)中的實(shí)踐
由于隊(duì)列設(shè)置過(guò)長(zhǎng),最大線程數(shù)設(shè)置失效,導(dǎo)致請(qǐng)求數(shù)量增加時(shí),大量任務(wù)堆積在隊(duì)列中,任務(wù)執(zhí)行時(shí)間過(guò)長(zhǎng),最終導(dǎo)致下游服務(wù)的大量調(diào)用超時(shí)失敗。
ThreadPoolExecutor的corePoolSize的值是可以設(shè)置的。利用這點(diǎn)加上配置中心,可以動(dòng)態(tài)的調(diào)整核心線程數(shù)。
四、線程池總結(jié)
做好業(yè)務(wù)線程池,分三個(gè)級(jí)別
第一級(jí)別,根據(jù)業(yè)務(wù)特性實(shí)現(xiàn)不同的業(yè)務(wù)線程池。
第二級(jí)別,根據(jù)業(yè)務(wù)特性,動(dòng)態(tài)調(diào)整線程池配置。
第三級(jí)別,實(shí)時(shí)監(jiān)控與配置線程池運(yùn)行情況。
到此這篇關(guān)于Java的ThreadPoolExecutor業(yè)務(wù)線程池詳細(xì)解析的文章就介紹到這了,更多相關(guān)ThreadPoolExecutor業(yè)務(wù)線程池內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
ZooKeeper框架教程Curator分布式鎖實(shí)現(xiàn)及源碼分析
本文是ZooKeeper入門系列教程,本篇為大家介紹zookeeper一個(gè)優(yōu)秀的框架Curator,提供了各種分布式協(xié)調(diào)的服務(wù),Curator中有著更為標(biāo)準(zhǔn)、規(guī)范的分布式鎖實(shí)現(xiàn)2022-01-01Java+swing實(shí)現(xiàn)經(jīng)典貪吃蛇游戲
貪吃蛇(也叫做貪食蛇)游戲是一款休閑益智類游戲,有PC和手機(jī)等多平臺(tái)版本。既簡(jiǎn)單又耐玩。本文將通過(guò)java的swing來(lái)實(shí)現(xiàn)這一游戲,需要的可以參考一下2022-01-01java父子線程之間實(shí)現(xiàn)共享傳遞數(shù)據(jù)
本文介紹了Java中父子線程間共享傳遞數(shù)據(jù)的幾種方法,包括ThreadLocal變量、并發(fā)集合和內(nèi)存隊(duì)列或消息隊(duì)列,并提醒注意并發(fā)安全問(wèn)題2025-02-02SpringBoot實(shí)現(xiàn)文章防盜鏈的代碼設(shè)計(jì)
這篇文章主要介紹了SpringBoot實(shí)現(xiàn)文章防盜鏈的代碼設(shè)計(jì),文中通過(guò)代碼示例講解的非常詳細(xì),對(duì)大家實(shí)現(xiàn)文章防盜鏈功能有一定的幫助,需要的朋友可以參考下2024-05-05java調(diào)用未知類的指定方法簡(jiǎn)單實(shí)例
這篇文章介紹了java調(diào)用未知類的指定方法簡(jiǎn)單實(shí)例,有需要的朋友可以參考一下2013-09-09