java高并发(十八)线程池
? ? 在之前已經(jīng)使用過線程池了。在使用中,基本上就是初始化好線程池的實例之后,把任務(wù)丟進(jìn)去,等待調(diào)度執(zhí)行就可以了。使用起來非常簡單方便。
new Thread弊端
- 每次new Thread新建對象,性能差
- 線程缺乏統(tǒng)一管理,可能無限制的新建線程,相互競爭,有可能占用過多系統(tǒng)資源導(dǎo)致死機(jī)或者OOM
- 缺少更多功能,如更多執(zhí)行、定期執(zhí)行、線程中斷
線程池的好處
- 重用存在的線程,減少對象創(chuàng)建、消亡的開銷,性能好
- 可以有效控制最大并發(fā)線程數(shù),提高系統(tǒng)資源利用率,同時可以避免過多資源競爭,避免阻塞
- 提供定時執(zhí)行、定期執(zhí)行、單線程、并發(fā)數(shù)控制等高級功能
ThreadPoolExecutor
參數(shù):
- corePoolSize:核心線程數(shù)量。建議和cpu的核心數(shù)差不多,當(dāng)有任務(wù)提交,檢測當(dāng)前線程池內(nèi)的線程數(shù)小于corePoolSize的話,新建線程執(zhí)行任務(wù),而不會開始復(fù)用,會新建,直到達(dá)到corePoolSize。線程池內(nèi)的線程數(shù)大于等于corePoolSize時,將任務(wù)放入workQueue等待。
- maximumPoolSize:允許線程池內(nèi)最大線程數(shù)。當(dāng)隊列滿了之后,如果線程池內(nèi)的線程數(shù)小于maximumPoolSize則新建線程,如果大于等于執(zhí)行拒絕策略。
如果maximumPoolSize是30,corePoolSize是10,當(dāng)隊列滿了后只能再開20個線程。 - workQueue:阻塞隊列,存儲等待執(zhí)行的任務(wù),很重要,會對線程池運(yùn)行過程產(chǎn)生重大影響
- keepAliveTime:線程沒有任務(wù)執(zhí)行時最多保持多久時間終止。線程池維護(hù)線程所允許的空閑時間。當(dāng)線程池中的線程數(shù)量大于CorePoolSize時,如果這時沒有新的任務(wù)提交,線程會等待,直到時間超過keepAliveTime才銷毀。
- unit:keepAliveTime的時間單位
- threadFactory:線程工廠,用來創(chuàng)建線程,會有一個默認(rèn)的工廠來創(chuàng)建線程。使用默認(rèn)的工廠創(chuàng)建線程時,線程擁有相同的優(yōu)先級,并且是非守護(hù)的線程,同時也設(shè)置了線程的名稱。
- rejectHandle:當(dāng)拒絕處理任務(wù)時的策略。如果workQueue阻塞隊列滿了,并且沒有空閑的線程時,這時還繼續(xù)提交任務(wù),我們就需要一種策略來處理這個任務(wù)。線程池總共提供了四種策略:
- 直接拋出異常,默認(rèn)策略
- ?用調(diào)用者所在的線程執(zhí)行任務(wù)
- 丟棄隊列中最靠前的任務(wù),并執(zhí)行當(dāng)前任務(wù)
- 直接丟棄這個任務(wù)
????如果運(yùn)行的線程數(shù)小于CorePoolSize時,直接創(chuàng)建新線程創(chuàng)建任務(wù),即使線程池中的其他線程是空閑的。
????如果線程池中的線程數(shù)量大于等于CorePoolSize且小于maximumPoolSize時,則只有當(dāng)wokQueue滿時才創(chuàng)建新的線程去處理任務(wù)。
????如果我們設(shè)置CorePoolSize與maximumPoolSize相等,那么創(chuàng)建的線程池大小是固定的,這時如果有新任務(wù)提交且workQueue還沒滿,就把請求放入workQueue中,等待空閑線程從workQueue中取任務(wù)進(jìn)行處理。
? ? 如果運(yùn)行的線程數(shù)量大于maximumPoolSize時,這時如果workQueue滿,那么會通過一個拒絕策略參數(shù)來指定策略處理任務(wù)。
? ? 如果我們想降低系統(tǒng)資源的消耗,包括CPU的使用率、操作系統(tǒng)資源的消耗,可以設(shè)置一個較大的workQueue容量和較小的CorePoolSize容量,這樣會降低線程處理的吞吐量。如果我們提交的任務(wù)經(jīng)常發(fā)生阻塞,我們可以設(shè)置maximumPoolSize來設(shè)置線程池容量。如果我們隊列容量較小,通常需要把maximumPoolSize設(shè)置大一些,這樣CPU使用率會高一些。但是如果線程池容量設(shè)置過大,在提交人物數(shù)量過多的情況下,并發(fā)量會增加,那么線程間資源調(diào)度就是一個需要考慮的問題,反而會降低處理的吞吐量。
線程池狀態(tài)
當(dāng)我們初始化一個線程池之后,通常有上面幾種狀態(tài)。
running:可以接受新提交的任務(wù),也能處理阻塞隊列中的任務(wù)。
shutdown:關(guān)閉狀態(tài),當(dāng)一個線程池實例處于shutdown狀態(tài)時,不能再接收新提交的任務(wù),但卻可以繼續(xù)處理阻塞隊列中已經(jīng)保存的任務(wù)。
stop:也不能接收新的任務(wù),也不處理隊列中的任務(wù)。會中斷正在處理的線程任務(wù)。
tidying:所有任務(wù)都已經(jīng)終止了,沒有活動中的線程。當(dāng)線程池進(jìn)行該狀態(tài)時候,會執(zhí)行鉤子方法terminated() 。
ThreadPoolExecutor中的方法
Executors框架接口
- Executors.newCachedThreadPool:創(chuàng)建一個可緩存的線程池,如果線程池的長度超過了線程的需要,可以靈活回收空閑線程,如果沒有回收的,可以新建線程
- Executors.newFixedThreadPool:創(chuàng)建一個定長的線程池,可以控制線程的最大并發(fā)數(shù),超出的線程會在隊列中等待
- Executors.newScheduledThreadPool:也是創(chuàng)建一個定長的線程池。支持定時以及周期性的任務(wù)執(zhí)行。
- Executors.newSingleThreadExecutor:創(chuàng)建一個單線程的線程池,會用唯一的工作線程來執(zhí)行任務(wù)。保證所有任務(wù)按照指定順序去執(zhí)行(先入先出等)。
?Executors.newCachedThreadPool示例代碼:
@Slf4j public class ThreadPoolExample1 {public static void main(String[] args) {ExecutorService executorService = Executors.newCachedThreadPool();for (int i = 0; i< 10; i++){final int index = i;executorService.execute(new Runnable() {@Overridepublic void run() {log.info("task:{}", index);}});}//一定要關(guān)閉線程池,否則程序不結(jié)束executorService.shutdown();} }Executors.newSingleThreadExecutor示例代碼:
@Slf4j public class ThreadPoolExample3 {public static void main(String[] args) {ExecutorService executorService = Executors.newSingleThreadExecutor();for (int i = 0; i< 10; i++){final int index = i;executorService.execute(new Runnable() {@Overridepublic void run() {log.info("task:{}", index);}});}//一定要關(guān)閉線程池,否則程序不結(jié)束executorService.shutdown();} }輸出結(jié)果:
17:08:16.870 [pool-1-thread-1] INFO com.vincent.example.threadPool.ThreadPoolExample3 - task:0 17:08:16.875 [pool-1-thread-1] INFO com.vincent.example.threadPool.ThreadPoolExample3 - task:1 17:08:16.875 [pool-1-thread-1] INFO com.vincent.example.threadPool.ThreadPoolExample3 - task:2 17:08:16.875 [pool-1-thread-1] INFO com.vincent.example.threadPool.ThreadPoolExample3 - task:3 17:08:16.875 [pool-1-thread-1] INFO com.vincent.example.threadPool.ThreadPoolExample3 - task:4 17:08:16.875 [pool-1-thread-1] INFO com.vincent.example.threadPool.ThreadPoolExample3 - task:5 17:08:16.875 [pool-1-thread-1] INFO com.vincent.example.threadPool.ThreadPoolExample3 - task:6 17:08:16.875 [pool-1-thread-1] INFO com.vincent.example.threadPool.ThreadPoolExample3 - task:7 17:08:16.875 [pool-1-thread-1] INFO com.vincent.example.threadPool.ThreadPoolExample3 - task:8 17:08:16.875 [pool-1-thread-1] INFO com.vincent.example.threadPool.ThreadPoolExample3 - task:9相當(dāng)于單線程按順序執(zhí)行。??
Executors.newScheduledThreadPool代碼示例:
@Slf4j public class ThreadPoolExample4 {public static void main(String[] args) {ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);scheduledExecutorService.scheduleAtFixedRate(new Runnable() {@Overridepublic void run() {log.warn("schedule run");}},1, 3, TimeUnit.SECONDS);//這里不需要關(guān)閉線程池//scheduledExecutorService.shutdown();} }上面目的是:延遲1秒后,每個三秒執(zhí)行一次任務(wù),由于這個任務(wù)時不定的執(zhí)行,因此這里不應(yīng)該關(guān)閉線程池。如果需要關(guān)閉線程池的話,可以設(shè)置一個觸發(fā)條件來關(guān)閉。類似于Timer類:
@Slf4j public class ThreadPoolExample4 {public static void main(String[] args) {Timer timer = new Timer();timer.schedule(new TimerTask() {@Overridepublic void run() {log.warn("timer run");}}, new Date(), 5*1000);} }線程池合理配置
- CPU密集型任務(wù),就需要盡量壓榨CPU,參考值可以設(shè)置為NCPU+1
- 如果是IO密集型任務(wù),參考值可以設(shè)置為2*NCPU
????我們只用線程池主要是為了重用存在的線程,減少對象創(chuàng)建消亡,能有效控制最大線程并發(fā)數(shù),可以避免過多的資源競爭和阻塞,也可以定時執(zhí)行單線程與控制線程的執(zhí)行性能比較好。?這不代表線程池應(yīng)該隨時隨地用,一定要根據(jù)自己的實際場景來分析使用參數(shù)配置
總結(jié)
以上是生活随笔為你收集整理的java高并发(十八)线程池的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java高并发(十七)J.U.C之Blo
- 下一篇: java高并发(十九)死锁、spring