線程池知識(shí)點(diǎn)詳解
點(diǎn)擊上方藍(lán)色字體,選擇“標(biāo)星公眾號(hào)”
優(yōu)質(zhì)文章,第一時(shí)間送達(dá)
? 作者?|??萌新J
來(lái)源 |? urlify.cn/A7BrIv
66套java從入門到精通實(shí)戰(zhàn)課程分享
文章正文:
引入
為什么使用線程池?
在連接數(shù)少的情況下,對(duì)于需要線程的地方我們只需要直接新建線程來(lái)處理就可以了,但是在并發(fā)量高的場(chǎng)景下,頻繁的線程創(chuàng)建、銷毀是非常消耗資源的,所以針對(duì)于這樣的場(chǎng)景可以使用線程池,讓一開(kāi)始就創(chuàng)建好線程,在需要新連接進(jìn)來(lái)需要線程時(shí)就從線程池中拿一條執(zhí)行,完成后再將線程放回線程池,等到其他線程需要時(shí)再獲取就可以了,這樣可以有效提高系統(tǒng)整體的性能。?
線程池的好處?適應(yīng)場(chǎng)景?
好處:1、降低資源損耗 2、響應(yīng)速度快 3、方便線程管理 4、提供定時(shí)執(zhí)行、定期執(zhí)行、并發(fā)數(shù)控制等功能。適用場(chǎng)景:并發(fā)量大,IO操作多,需要頻繁創(chuàng)建線程的場(chǎng)景。
阻塞隊(duì)列
?阻塞隊(duì)列是一個(gè)支持兩個(gè)附加操作的隊(duì)列,其本質(zhì)還是一個(gè)隊(duì)列,當(dāng)內(nèi)部存儲(chǔ)的數(shù)據(jù)量超過(guò)當(dāng)前阻塞隊(duì)列的容量時(shí),就會(huì)阻塞,停止接收新的數(shù)據(jù);同樣,如果內(nèi)部存儲(chǔ)的數(shù)據(jù)量變?yōu)?,那么也會(huì)阻塞,外界的數(shù)據(jù)請(qǐng)求也會(huì)不再接收。
種類
1、ArrayBlockingQueue:由數(shù)組結(jié)構(gòu)組成的有界阻塞隊(duì)列
2、LinkedBlockingQueue:由鏈表組成的有界(但大小默認(rèn)值為Integer.MAX_Value)
3、PriorityBlockingQueue:支持優(yōu)先級(jí)排序的無(wú)界阻塞隊(duì)列
4、DelayQueue:使用優(yōu)先級(jí)隊(duì)列實(shí)現(xiàn)的延遲無(wú)界阻塞隊(duì)列
5、SynchronizedQueue:不存儲(chǔ)元素的阻塞隊(duì)列,也即單個(gè)元素的隊(duì)列
6、LinkedTransferQueue:由鏈表結(jié)構(gòu)組成的無(wú)界阻塞隊(duì)列
7、LinkedBlockingDeque:由鏈表結(jié)構(gòu)組成的雙向阻塞隊(duì)列
其中橘色的三種是常用的。其中 LinkedBlockingQueue 和 SynchronizedQueue 是兩個(gè)極端,SynchronizedQueue 是沒(méi)有容量的阻塞隊(duì)列,而 LinkedBlockingQueue 在未指定容量時(shí)可以看作是容量無(wú)窮大的阻塞隊(duì)列。
核心方法
| 方法類型 | 拋出異常 | 特殊值 | 阻塞 | 超時(shí) |
| 插入 | add(e) | offer(e) | put(e) | offer(e,time,unit) |
| 移除 | remove() | poll() | take() | poll(time,unit) |
| 檢查 | element() | peek() | 不可用 | 不可用 |
檢查是在有數(shù)據(jù)時(shí)返回隊(duì)列的第一個(gè)數(shù)據(jù),并不會(huì)從隊(duì)列中移除該數(shù)據(jù)。內(nèi)部使用 ReentrantLock 進(jìn)行同步控制的。
| 拋出異常 | 當(dāng)阻塞隊(duì)列滿時(shí),再往隊(duì)列里add插入元素會(huì)拋lllegalStateException:Queue full 當(dāng)阻塞隊(duì)列空時(shí),再往隊(duì)列里remove移除元素會(huì)拋NoSuchElementException |
| 特殊值 | 插入方法,成功true失敗false 移除方法,成功返回出隊(duì)列的元素,沒(méi)有元素返回null |
| 阻塞 | 隊(duì)列滿時(shí)put,隊(duì)列會(huì)一直阻塞直到put數(shù)據(jù)或者響應(yīng)中斷退出 隊(duì)列為空時(shí)take,隊(duì)列會(huì)一直阻塞直到隊(duì)列可用 |
| 超時(shí) | 當(dāng)隊(duì)列滿時(shí),隊(duì)列會(huì)阻塞生產(chǎn)者線程一定時(shí)間,超時(shí)后限時(shí)后生產(chǎn)者線程會(huì)退出 |
?
線程池
線程池的核心接口是 ExecutorService,它定義了線程池的各個(gè)基本抽象方法。

?
執(zhí)行機(jī)制

當(dāng)新的線程請(qǐng)求進(jìn)來(lái)時(shí),會(huì)先判斷核心線程數(shù)是否已滿,如果未滿則直接新建線程并執(zhí)行,執(zhí)行完將其放回線程池;
?如果已滿就再檢查隊(duì)列是否已滿,如果沒(méi)滿就將當(dāng)前線程請(qǐng)求加入阻塞隊(duì)列,等待空閑線程分配;
? 如果已滿就再檢查線程池當(dāng)前存在的線程數(shù)是否已達(dá)到規(guī)定的最大值,如果沒(méi)有達(dá)到就創(chuàng)建線程執(zhí)行;
如果達(dá)到就執(zhí)行對(duì)應(yīng)的飽和策略。
其中的名詞下面會(huì)解釋。
?
種類
ThreadPoolExecutor
首先看一下《阿里巴巴Java開(kāi)發(fā)手冊(cè)》中推薦的線程池創(chuàng)建,這也是線程池的最基本創(chuàng)建方式。那就是使用創(chuàng)建 ThreadPoolExecutor 對(duì)象來(lái)作為線程池,首先看一下它的構(gòu)造器

?可以看到它有四種構(gòu)造函數(shù),但前三種的實(shí)現(xiàn)本質(zhì)還是使用第四種實(shí)現(xiàn)的,只不過(guò)使用的都是對(duì)應(yīng)默認(rèn)配置而已。



?所以我們著重看一下第四個(gè)構(gòu)造函數(shù)。
public?ThreadPoolExecutor(int?corePoolSize,
??????????????????????????????int?maximumPoolSize,
??????????????????????????????long?keepAliveTime,
??????????????????????????????TimeUnit?unit,
??????????????????????????????BlockingQueue?workQueue,
??????????????????????????????ThreadFactory?threadFactory,
??????????????????????????????RejectedExecutionHandler?handler)?{
????????if?(corePoolSize?0?||
????????????maximumPoolSize?<=?0?||
????????????maximumPoolSize?????????????keepAliveTime?0)
????????????throw?new?IllegalArgumentException();
????????if?(workQueue?==?null?||?threadFactory?==?null?||?handler?==?null)
????????????throw?new?NullPointerException();
????????this.acc?=?System.getSecurityManager()?==?null??
????????????????null?:
????????????????AccessController.getContext();
????????this.corePoolSize?=?corePoolSize;
????????this.maximumPoolSize?=?maximumPoolSize;
????????this.workQueue?=?workQueue;
????????this.keepAliveTime?=?unit.toNanos(keepAliveTime);
????????this.threadFactory?=?threadFactory;
????????this.handler?=?handler;
????}
內(nèi)容是對(duì)應(yīng)屬性的賦值,方法參數(shù)從左到右分別為核心線程數(shù)、線程池允許同時(shí)存在的最大線程數(shù)、線程的最大存活時(shí)間、最大存活時(shí)間的單位、阻塞隊(duì)列、線程工廠、拒絕策略。?
? 核心線程數(shù)、最大線程數(shù)、阻塞隊(duì)列和拒絕策略就是上面執(zhí)行機(jī)制中提到的。最大存活時(shí)間是指非核心線程在執(zhí)行完代碼后回到線程池,在經(jīng)過(guò)最大存活時(shí)間后仍然沒(méi)有任務(wù)分配給它,那么它就會(huì)被回收。核心線程則不會(huì)被回收,所以核心線程數(shù)就規(guī)定了線程池的最小線程數(shù)(當(dāng)前線程池剛剛被創(chuàng)建時(shí)為0,直到有線程請(qǐng)求進(jìn)來(lái)后才會(huì)開(kāi)始創(chuàng)建線程)。線程工廠是指定創(chuàng)建線程的工廠,這樣在創(chuàng)建線程可以更方便。拒絕策略是指在阻塞隊(duì)列滿以及線程池容納的線程數(shù)也達(dá)到最大線程池后執(zhí)行的策略。
拒絕策略包括以下幾種:
1、ThreadPoolExecutor.AbortPolicy:新任務(wù)提交直接拋出異常,RejectedExecutionException(默認(rèn))
2、ThreadPoolExecutor.CallerRunsPolicy:即不拋棄任務(wù)也不拋出異常,而是將任務(wù)返回給調(diào)用者。不會(huì)在線程池中執(zhí)行,而是在調(diào)用executor方法的線程中執(zhí)行(也就是傳進(jìn)來(lái)的Runnble對(duì)象來(lái)創(chuàng)建線程啟動(dòng)運(yùn)行),會(huì)降低新任務(wù)的提交速度,影響程序的整體性能。
3、ThreadPoolExecutor.DiscardPolicy:直接拋棄新提交的任務(wù)。
4、ThreadPoolExecutor.DiscardOldestPolicy:拋棄最早加入阻塞隊(duì)列的請(qǐng)求。
需要注意的是這些拒絕策略其實(shí)是 ThreadPoolExecutor 的內(nèi)部類。
?
關(guān)于核心線程數(shù)的設(shè)置,可以參考下面的配置
1、對(duì)于CPU密集型,也就是代碼大部分操作都是CPU去執(zhí)行計(jì)算處理的,不需要?jiǎng)?chuàng)建過(guò)多的線程,所以可以設(shè)置為?CPU核數(shù)+1
2、對(duì)于IO密集型,因?yàn)镮O操作往往伴隨著線程的線程的使用,所以應(yīng)該設(shè)置大一些,所以可以設(shè)置為?CPU核數(shù)*2
?
線程池的狀態(tài)
1、Running。運(yùn)行中,線程池正常執(zhí)行,當(dāng)線程池被創(chuàng)建后就會(huì)進(jìn)入 Running 狀態(tài)。
2、ShutDown。關(guān)閉,不會(huì)再接受新的線程請(qǐng)求,但是還是會(huì)處理阻塞隊(duì)列中的請(qǐng)求。當(dāng)調(diào)用對(duì)象的 shutdown 方法后就會(huì)進(jìn)入該狀態(tài)。
3、Stop。停止,不會(huì)再接受新的線程請(qǐng)求,也不會(huì)再處理阻塞隊(duì)列中的請(qǐng)求。當(dāng)調(diào)用對(duì)象的 shutdownNow 方法后就會(huì)進(jìn)入該狀態(tài)。
4、Tidying。進(jìn)入該狀態(tài)會(huì)開(kāi)始執(zhí)行線程池的 terminated 方法。在 ShutDown 狀態(tài)中阻塞隊(duì)列為空,同時(shí)線程池中的工作線程數(shù)為0時(shí)就會(huì)進(jìn)入該狀態(tài);在 Stop 狀態(tài)中工作線程數(shù)為0就會(huì)進(jìn)入該狀態(tài)。
5、Terminated。終止。表示線程池正式停止工作。當(dāng)在 Tidying 狀態(tài)中執(zhí)行完 terminated 方法后就會(huì)進(jìn)入該狀態(tài)。
?
常用方法
1、execute(Runnable):處理 Ruunable 類型線程請(qǐng)求
2、submit(Runnable)、submit(Callable
3、shutdown():進(jìn)入 ShutDown 狀態(tài)
4、shutdownNow():進(jìn)入 Stop 狀態(tài)
5、terminated():線程池停止前執(zhí)行的方法,空方法,子類可以來(lái)重寫(xiě)自定義。
6、getTaskCount():獲取線程池接收的任務(wù)總數(shù)
7、getCompletedTaskCount():獲取線程池已完成的任務(wù)數(shù)
8、getPoolSize():獲取線程池的線程數(shù)量
9、getActiveCount():獲取線程池正在執(zhí)行的線程數(shù)
?
?
其他封裝好的線程池
對(duì)于直接創(chuàng)建 ThreadPoolExecutor 對(duì)象來(lái)實(shí)現(xiàn)線程池的創(chuàng)建,過(guò)程比較復(fù)雜,當(dāng)然在實(shí)際開(kāi)發(fā)中還是推薦這種方式,而在某些場(chǎng)景中則不需要定義這么規(guī)范的線程池,所以在 Executors 工具類中為我們封裝了幾種線程池,我們只需要調(diào)用方法就可以獲取對(duì)應(yīng)的線程池。
1、Executors.newSingletonThreadExecutor。方法源碼如下。
public?static?ExecutorService?newSingleThreadExecutor()?{
????????return?new?FinalizableDelegatedExecutorService
????????????(new?ThreadPoolExecutor(1,?1,
????????????????????????????????????0L,?TimeUnit.MILLISECONDS,
????????????????????????????????????new?LinkedBlockingQueue()));
????} 可以看到這個(gè)線程池就是一個(gè)單線程的線程池,只能存儲(chǔ)一個(gè)線程,但是它的阻塞隊(duì)列是 LinkedBlockingQueue,所以意味著阻塞隊(duì)列的容量可以看作是無(wú)限大的。
?
2、Executors.newFixedThreadPool(int)。方法源碼如下。
public?static?ExecutorService?newFixedThreadPool(int?nThreads)?{
????????return?new?ThreadPoolExecutor(nThreads,?nThreads,
??????????????????????????????????????0L,?TimeUnit.MILLISECONDS,
??????????????????????????????????????new?LinkedBlockingQueue());
????} 這個(gè)線程池的最大線程數(shù)是傳參值,核心線程數(shù)也是傳參值,這意味著在工作線程執(zhí)行完后回到線程池永遠(yuǎn)不會(huì)被回收,使用的阻塞隊(duì)列也是 LinkedBlockingQueue。
?
3、Executors.newCachedThreadPool()。方法源碼如下。
public?static?ExecutorService?newCachedThreadPool()?{
????????return?new?ThreadPoolExecutor(0,?Integer.MAX_VALUE,
??????????????????????????????????????60L,?TimeUnit.SECONDS,
??????????????????????????????????????new?SynchronousQueue());
????} 這里的核心線程數(shù)是0,也就是線程池在空閑時(shí)會(huì)回收所有的線程,但是最大線程數(shù)是 Integer的最大范圍,所以可以看作可以同時(shí)包括無(wú)限大的線程,并且使用的阻塞隊(duì)列是 SynchronousQueue,所以當(dāng)線程請(qǐng)求進(jìn)來(lái)時(shí)總會(huì)立即創(chuàng)建線程執(zhí)行。
?
4、Executors.newScheduledThreadPool(int)。方法源碼如下。
public?static?ScheduledExecutorService?newScheduledThreadPool(int?corePoolSize)?{
????????return?new?ScheduledThreadPoolExecutor(corePoolSize);
????}
?public?ScheduledThreadPoolExecutor(int?corePoolSize)?{
????????super(corePoolSize,?Integer.MAX_VALUE,?0,?NANOSECONDS,
??????????????new?DelayedWorkQueue());
????}
這個(gè)線程池的核心線程數(shù)是指定的參數(shù),最大線程數(shù)同樣是無(wú)限大,阻塞隊(duì)列是 DelayedWorkQueue(),默認(rèn)容量是16。
?
上面四種封裝好的線程池都有缺陷,前兩個(gè)因?yàn)樽枞?duì)列是 LinkedBlockingQueue,所以在大量的線程請(qǐng)求進(jìn)來(lái)時(shí)大部分會(huì)存儲(chǔ)在阻塞隊(duì)列中,最終撐爆堆空間,拋出OOM;而后兩個(gè)因?yàn)樵试S的最大線程數(shù)是 Integer.MAX_VALUE,所以可以看作是無(wú)限大的,所以在大量的線程請(qǐng)求進(jìn)來(lái)時(shí)也會(huì)因?yàn)閯?chuàng)建過(guò)多的線程數(shù)而拋出OOM。所以這四種線程池需要慎用。
粉絲福利:實(shí)戰(zhàn)springboot+CAS單點(diǎn)登錄系統(tǒng)視頻教程免費(fèi)領(lǐng)取
???
?長(zhǎng)按上方微信二維碼?2 秒 即可獲取資料
感謝點(diǎn)贊支持下哈?
