JUC系列(九)| ThreadPool 线程池
多線程一直Java開發(fā)中的難點(diǎn),也是面試中的常客,趁著還有時(shí)間,打算鞏固一下JUC方面知識,我想機(jī)會隨處可見,但始終都是留給有準(zhǔn)備的人的,希望我們都能加油!!!
沉下去,再浮上來,我想我們會變的不一樣的。
🍟線程池介紹
1)什么是線程池?
線程池(英語:thread pool):一種線程使用模式。由系統(tǒng)維護(hù)的容納線程的容器,由CLR控制的所有AppDomain共享。線程池可用于執(zhí)行任務(wù)、發(fā)送工作項(xiàng)、處理異步 I/O、代表其他線程等待以及處理計(jì)時(shí)器。
2)為什么要使用線程池?
痛點(diǎn):
原因:
線程池維護(hù)著多個(gè)線程,等待著監(jiān)督管理者分配可并發(fā)執(zhí)行的任務(wù)。這避免了在處理短時(shí)間任務(wù)時(shí)創(chuàng)建與銷毀線程的代價(jià)。線程池不僅能夠保證內(nèi)核的充分利用,還能防止過分調(diào)度。
線程池的優(yōu)勢:
線程池做的工作只要是控制運(yùn)行的線程數(shù)量,處理過程中將任務(wù)放入隊(duì)列,然后在線程創(chuàng)建后啟動這些任務(wù),如果線程數(shù)量超過了最大數(shù)量, 超出數(shù)量的線程排隊(duì)等候,等其他線程執(zhí)行完畢,再從隊(duì)列中取出任務(wù)來執(zhí)行。
另外在我們常常看的阿里巴巴Java開發(fā)手冊也有提到:
【強(qiáng)制】線程資源必須通過線程池提供,不允許在應(yīng)用中自行顯式創(chuàng)建線程。
說明:使用線程池的好處是減少在創(chuàng)建和銷毀線程上所花的時(shí)間以及系統(tǒng)資源的開銷,解決資源不足的問題。如果不使用線程池,有可能造成系統(tǒng)創(chuàng)建大量同類線程而導(dǎo)致消耗完內(nèi)存或者“過度切換”的問題。
3)特點(diǎn)
降低資源消耗:通過重復(fù)利用已創(chuàng)建的線程降低線程創(chuàng)建和銷毀造成的銷耗。
提高響應(yīng)速度:當(dāng)任務(wù)到達(dá)時(shí),任務(wù)可以不需要等待線程創(chuàng)建就能立即執(zhí)行。
提高線程的可管理性: 線程是稀缺資源,如果無限制的創(chuàng)建,不僅會消耗系統(tǒng)資源,還會降低系統(tǒng)的穩(wěn)定性,使用線程池可以進(jìn)行統(tǒng)一的分配,調(diào)優(yōu)和監(jiān)控。
4)架構(gòu)
Java 中的線程池是通過 Executor 框架實(shí)現(xiàn)的,該框架中用到了 Executor, ExecutorService,ThreadPoolExecutor, 這幾個(gè)類
另外還有Executors這個(gè)方便封裝的工具類。但是不建議使用😂
5)線程池參數(shù)說明
ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler)- 當(dāng)提交一個(gè)任務(wù)時(shí),如果當(dāng)前核心線程池的線程個(gè)數(shù)沒有達(dá)到 corePoolSize,則會創(chuàng)建新的線程來執(zhí)行所提交的任務(wù),即使當(dāng)前核心線程池有空閑的線程。如果當(dāng)前核心線程池的線程個(gè)數(shù)已經(jīng)達(dá)到了 corePoolSize,則不再重新創(chuàng)建線程。
- 即當(dāng)阻塞隊(duì)列已滿時(shí),并且當(dāng)前線程池線程個(gè)數(shù)沒有超過 maximumPoolSize 的話,就會創(chuàng)建新的線程來執(zhí)行任務(wù)。
- 如果當(dāng)前線程池的線程個(gè)數(shù)已經(jīng)超過了 corePoolSize,并且線程空閑時(shí)間超過了 keepAliveTime 的話,就會將這些空閑線程銷毀,這樣可以盡可能降低系統(tǒng)資源消耗。
線程池中,有三個(gè)重要的參數(shù),決定影響了拒絕策略:
6)拒絕策略
AbortPolicy: 丟棄任務(wù),并拋出拒絕執(zhí)行 RejectedExecutionException 異常信息。線程池默認(rèn)的拒絕策略。必須處理好拋出的異常,否則會打斷當(dāng)前的執(zhí)行流程,影響后續(xù)的任務(wù)執(zhí)行。
CallerRunsPolicy: 當(dāng)觸發(fā)拒絕策略,只要線程池沒有關(guān)閉的話,則使用調(diào)用線程直接運(yùn)行任務(wù)。一般并發(fā)比較小,性能要求不高,不允許失敗。但是,由 于調(diào)用者自己運(yùn)行任務(wù),如果任務(wù)提交速度過快,可能導(dǎo)致程序阻塞,性能效 率上必然的損失較大
DiscardPolicy: 直接丟棄.
DiscardOldestPolicy: 觸發(fā)拒絕策略時(shí),只要線程池沒有關(guān)閉的話,丟棄阻塞隊(duì)列 workQueue中最老的任務(wù),并將新任務(wù)加入
🧇線程池種類
Jav有4種默認(rèn)線程池,分別是:
2.1、 newCachedThreadPool
作用:創(chuàng)建一個(gè)可緩存線程池,此線程池不會對線程池大小做限制,線程池大小完全依賴于操作系統(tǒng)(或者說JVM)能夠創(chuàng)建的最大線程大小。如果線程池長度超過處理需要,可靈活回收空閑線程,若無可回收,則新建線程.
比較適合用于創(chuàng)建一個(gè)可無限擴(kuò)大的線程池,執(zhí)行時(shí)間較短,任務(wù)多的場景。
特點(diǎn):
創(chuàng)建方式:
/*** 可緩存線程池* @return*/ public static ExecutorService newCachedThreadPool() {/*** corePoolSize 線程池的核心線程數(shù)* maximumPoolSize 能容納的最大線程數(shù)* keepAliveTime 空閑線程存活時(shí)間* unit 存活的時(shí)間單位* workQueue 存放提交但未執(zhí)行任務(wù)的隊(duì)列* threadFactory 創(chuàng)建線程的工廠類:可以省略* handler 等待隊(duì)列滿后的拒絕策略:可以省略*/return new ThreadPoolExecutor(0,Integer.MAX_VALUE,60L,TimeUnit.SECONDS,new SynchronousQueue<>(),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy()); }2.2、newFixedThreadPool
作用:
用來創(chuàng)建一個(gè)可重用固定線程數(shù)的線程池,以共享的無界隊(duì)列(LinkedBlockingQueue)方式來運(yùn)行這些線程。
適用于預(yù)測并發(fā)壓力的情況下,對線程數(shù)做出限制;或者對線程數(shù)有嚴(yán)格限制的場景。
特點(diǎn):
創(chuàng)建方式:
public static ExecutorService newFixedThreadPool(int nThreads) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()); }2.3、newSingleThreadExecutor
作用:
創(chuàng)建一個(gè)使用單個(gè) worker 線程的 Executor,以無界隊(duì)列(LinkedBlockingQueue)方式來運(yùn)行該線程。超出的任務(wù)都將存儲在隊(duì)列中,等待執(zhí)行。
適用于需要保證順序執(zhí)行各個(gè)任務(wù),并且在任意時(shí)間點(diǎn),不會同時(shí)有多個(gè)線程的場景
特點(diǎn):
線程池中最多執(zhí)行 1 個(gè)線程,之后提交的線程活動將會排在隊(duì)列中以此 執(zhí)行
創(chuàng)建方式:
public static ExecutorService newSingleThreadExecutor() {return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>())); }2.4、newScheduledThreadPool
作用:
創(chuàng)建一個(gè) corePoolSize 為傳入?yún)?shù),最大線程數(shù)為(Integer.MAX_VALUE)的線程池,此線程池支持定時(shí)以及周期性執(zhí)行任務(wù)的需求。
比較適用于需要多個(gè)后臺線程執(zhí)行周期任務(wù)的場景,某個(gè)時(shí)候要收集日志了,發(fā)送推送消息拉等等都很合適😂
特點(diǎn):
可定時(shí)或延遲執(zhí)行線程活動~~(我拿這個(gè)模擬過動態(tài)定時(shí),意思是給定個(gè)未來某個(gè)時(shí)間,然后到那個(gè)時(shí)間點(diǎn)就執(zhí)行)~~
創(chuàng)建方式:
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory) {return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory); }public ScheduledThreadPoolExecutor(int corePoolSize,ThreadFactory threadFactory) {super(corePoolSize, Integer.MAX_VALUE,DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,new DelayedWorkQueue(), threadFactory); }public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory) {this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,threadFactory, defaultHandler); }🍖入門案例
火車站 3 個(gè)售票口, 10 個(gè)用戶買票
import java.util.concurrent.*;/*** 入門案例*/ public class ThreadPoolDemo1 {/*** 火車站 3 個(gè)售票口, 10 個(gè)用戶買票** @param args*/public static void main(String[] args) {//定時(shí)線程次:線程數(shù)量為 3---窗口數(shù)為 3ExecutorService threadService = new ThreadPoolExecutor(3,3,60L,TimeUnit.SECONDS,new LinkedBlockingQueue<>(),Executors.defaultThreadFactory(),new ThreadPoolExecutor.DiscardOldestPolicy());try {//10 個(gè)人買票for (int i = 1; i <= 10; i++) {threadService.execute(() -> {try {System.out.println(Thread.currentThread().getName() + " 窗口,開始賣票");Thread.sleep(500);System.out.println(Thread.currentThread().getName() + " 窗口買票結(jié)束");} catch (Exception e) {e.printStackTrace();}});}} catch (Exception e) {e.printStackTrace();} finally {//完成后結(jié)束threadService.shutdown();}} } /**pool-1-thread-3 窗口,開始賣票pool-1-thread-2 窗口,開始賣票pool-1-thread-1 窗口,開始賣票pool-1-thread-3 窗口買票結(jié)束pool-1-thread-2 窗口買票結(jié)束pool-1-thread-1 窗口買票結(jié)束....*/🍤線程池底層工作原理(重要)
public void execute(Runnable command) {if (command == null)throw new NullPointerException();int c = ctl.get();//如果線程池的線程個(gè)數(shù)少于corePoolSize則創(chuàng)建新線程執(zhí)行當(dāng)前任務(wù)if (workerCountOf(c) < corePoolSize) {//執(zhí)行addworker,創(chuàng)建一個(gè)核心線程,創(chuàng)建失敗重新獲取ctlif (addWorker(command, true))return;c = ctl.get();} //如果工作線程數(shù)大于核心線程數(shù),判斷線程池的狀態(tài)是否為running,并且可以添加進(jìn)隊(duì)列 //如果線程池不是running狀態(tài),則執(zhí)行拒絕策略,(還是會調(diào)用一次addworker)if (isRunning(c) && workQueue.offer(command)) {//再次獲取ctl,進(jìn)行雙重檢索int recheck = ctl.get();//如果線程池是不是處于RUNNING的狀態(tài),那么就會將任務(wù)從隊(duì)列中移除, //如果移除失敗,則會判斷工作線程是否為0 ,如果過為0 就創(chuàng)建一個(gè)非核心線程 //如果移除成功,就執(zhí)行拒絕策略,因?yàn)榫€程池已經(jīng)不可用了;if (! isRunning(recheck) && remove(command))//為給定的命令調(diào)用被拒絕的執(zhí)行處理程序reject(command);else if (workerCountOf(recheck) == 0)addWorker(null, false);}//檢查是否可以根據(jù)當(dāng)前池狀態(tài)和給定界限(核心或最大值)添加新的工作線程。 //如果是這樣,則相應(yīng)地調(diào)整工作人員數(shù)量,并且如果可能,將創(chuàng)建并啟動一個(gè)新工作人員,將 firstTask 作為其第一個(gè)任務(wù)運(yùn)行。 //如果池已停止或有資格關(guān)閉,則此方法返回 false。 //如果線程工廠在詢問時(shí)未能創(chuàng)建線程,它也會返回 false。 //如果線程創(chuàng)建失敗,要么是由于線程工廠返回 null,要么是由于異常(通常是 Thread.start() 中的 OutOfMemoryError),我們會干凈利落地回滾。else if (!addWorker(command, false))// 為給定的命令調(diào)用被拒絕的執(zhí)行處理程序reject(command); }上圖取自:線程池ThreadPoolExecutor實(shí)現(xiàn)原理 作者:你聽___
execute執(zhí)行邏輯:
- 如果正在運(yùn)行的線程數(shù)量小于 corePoolSize,那么馬上創(chuàng)建線程運(yùn)行這個(gè)任務(wù);
- 如果正在運(yùn)行的線程數(shù)量大于或等于 corePoolSize,那么將這個(gè)任務(wù)放入 隊(duì)列;
- 如果這個(gè)時(shí)候隊(duì)列滿了且正在運(yùn)行的線程數(shù)量還小于 maximumPoolSize,那么還是要?jiǎng)?chuàng)建非核心線程立刻運(yùn)行這個(gè)任務(wù);
- 如果隊(duì)列滿了且正在運(yùn)行的線程數(shù)量大于或等于 maximumPoolSize,那么線程 池會啟動飽和拒絕策略來執(zhí)行。
🍦注意事項(xiàng)
創(chuàng)建線程池推薦適用 ThreadPoolExecutor 及其 7 個(gè)參數(shù)手動創(chuàng)建
為什么不使用Executors創(chuàng)建?
🍕自言自語
最近又開始了JUC的學(xué)習(xí),感覺Java內(nèi)容真的很多,但是為了能夠走的更遠(yuǎn),還是覺得應(yīng)該需要打牢一下基礎(chǔ)。
最近在持續(xù)更新中,如果你覺得對你有所幫助,也感興趣的話,關(guān)注我吧,讓我們一起學(xué)習(xí),一起討論吧。
你好,我是博主寧在春,Java學(xué)習(xí)路上的一顆小小的種子,也希望有一天能扎根長成蒼天大樹。
希望與君共勉😁
我們:待別時(shí)相見時(shí),都已有所成。
參考:
線程池ThreadPoolExecutor實(shí)現(xiàn)原理
https://www.zhihu.com/question/23212914/answer/245992718 作者:大閑人柴毛毛
阿里巴巴開發(fā)手冊
總結(jié)
以上是生活随笔為你收集整理的JUC系列(九)| ThreadPool 线程池的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java | 设计模式-适配器模式
- 下一篇: 为什么阿里巴巴开发手册中强制要求 POJ