線程池介紹:
使用線程池的好處有很多,比如節(jié)省系統(tǒng)資源的開銷,節(jié)省創(chuàng)建和銷毀線程的時(shí)間等,當(dāng)我們需要處理的任務(wù)較多時(shí),就可以使用線程池。
線程池是一種多線程處理形式,處理過(guò)程中將任務(wù)添加到隊(duì)列,然后在創(chuàng)建線程后自動(dòng)啟動(dòng)這些任務(wù)。線程池線程都是后臺(tái)線程。每個(gè)線程都使用默認(rèn)的堆棧大小,以默認(rèn)的優(yōu)先級(jí)運(yùn)行,并處于多線程單元中。如果某個(gè)線程在托管代碼中空閑(如正在等待某個(gè)事件),則線程池將插入另一個(gè)輔助線程來(lái)使所有處理器保持繁忙。如果所有線程池線程都始終保持繁忙,但隊(duì)列中包含掛起的工作,則線程池將在一段時(shí)間后創(chuàng)建另一個(gè)輔助線程但線程的數(shù)目永遠(yuǎn)不會(huì)超過(guò)最大值。超過(guò)最大值的線程可以排隊(duì),但他們要等到其他線程完成后才啟動(dòng)。
1.首先我們先看一下獲取四種線程池的代碼:
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10); ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); ExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(10); ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();
可以發(fā)現(xiàn)這四種線程池都是由Executors類生成的。依次點(diǎn)開四個(gè)方法的內(nèi)部實(shí)現(xiàn)發(fā)現(xiàn),它們最終調(diào)用的都是同一個(gè)ThreadPoolExecutor()的構(gòu)造器,而區(qū)別在于構(gòu)造器的參數(shù)不同。我們來(lái)看下ThreadPoolExecutor的參數(shù)列表:
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {
正是由于這幾個(gè)參數(shù)的不同導(dǎo)致了四種線程池的工作機(jī)制不同。參考源碼對(duì)于參數(shù)的注釋,我們列出參數(shù)的含義。
- corePoolSize:核心線程數(shù)量,常駐在線程池中的線程,即使它們是空閑的,也不會(huì)銷毀,除非設(shè)置allowCoreThreadTimeOut的值。
- maximumPoolSize:線程池最大線程數(shù)量
- keepAliveTime:超過(guò)核心數(shù)量的額外線程也就是非核心線程,在空閑指定的最大時(shí)間后被銷毀。(假設(shè)時(shí)間為5s,核心線程數(shù)為2,當(dāng)前線程為4,則超過(guò)核心線程數(shù)的其余兩個(gè)線程在空閑5秒后會(huì)被銷毀。)
- unit:時(shí)間單位
- workQueue:等待隊(duì)列
- threadFactory:生成線程的工廠
- handler:當(dāng)?shù)却?duì)列容量滿以及線程池?cái)?shù)量達(dá)到最大時(shí),如何處理新的任務(wù)。
AbortPolicy(默認(rèn)):直接拋出異常
CallerRunsPolicy:交給調(diào)用者所在線程執(zhí)行。(假設(shè)當(dāng)前調(diào)用者線程是Main,那么就交給Main處理)
DiscardOldestPolicy:丟棄最久未處理的任務(wù),再執(zhí)行當(dāng)前任務(wù)。(最久未處理的,在隊(duì)列中其實(shí)就是隊(duì)列頭節(jié)點(diǎn),查看源碼的確調(diào)用是poll()方法)
DiscardPolicy:丟掉該任務(wù),并且不拋異常。
線程池的工作機(jī)制:
當(dāng)持續(xù)往線程池添加任務(wù),
當(dāng)前線程數(shù)量小于核心線程數(shù)量的時(shí)候,新增線程。
當(dāng)前線程數(shù)量達(dá)到核心線程數(shù)量的時(shí)候,將任務(wù)放入等待隊(duì)列。
當(dāng)?shù)却?duì)列滿的時(shí)候,繼續(xù)創(chuàng)建新線程。
當(dāng)線程池?cái)?shù)量達(dá)到最大并且等待隊(duì)列也滿的時(shí)候,采取拒絕服務(wù)策略。
2.接下來(lái)我們就根據(jù)參數(shù)來(lái)分析不同的線程池:
FixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
我們可以看到corePoolSize核心線程數(shù)量和maximumPoolSize最大線程數(shù)量是一致的,并且keepAliveTime為0。workQueue是LinkedBlockingQueue,這是一個(gè)鏈表阻塞隊(duì)列。可以得出結(jié)論:該線程池是一個(gè)固定數(shù)量的線程池,并且有一個(gè)無(wú)界的等待隊(duì)列。我們可以推導(dǎo)出該線程池適合處理任務(wù)量平穩(wěn)的場(chǎng)景。例如平均一秒接收10個(gè)任務(wù),接收任務(wù)量曲線不會(huì)很陡峭。
適合場(chǎng)景:適合少量的大任務(wù)(大任務(wù)處理慢,如果線程數(shù)量多的話,反而在切換線程上下文時(shí)損耗,所以控制線程在一定的數(shù)量)。
CachedThreadPool
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
我們可以看到corePoolSize核心線程池為0,代表該線程沒(méi)有核心線程池,意味著線程都是可被回收銷毀的,線程池中有時(shí)會(huì)是空的。并且maximumPoolSize是int最大值,相當(dāng)于代表該線程池可以無(wú)限創(chuàng)建線程。keepAliveTime為60,代表空閑60秒回收線程。workQueue是SynchronousQueue,該同步隊(duì)列是一個(gè)沒(méi)有容量隊(duì)列,即一個(gè)任務(wù)到來(lái)后,要等待線程來(lái)消費(fèi),才能再繼續(xù)添加任務(wù)。我們推導(dǎo)出該線程池適合處理平時(shí)沒(méi)什么任務(wù)量,但有時(shí)任務(wù)量瞬間劇增的場(chǎng)景。
適合場(chǎng)景:大量的小任務(wù)(每個(gè)任務(wù)處理快,不會(huì)頻繁出現(xiàn)線程處理一半時(shí),切換其他線程)。
ScheduledThreadPool
public ScheduledThreadPoolExecutor(int corePoolSize) { super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue()); }
我們可以看到該線程池的corePoolSize核心線程數(shù)量和maximumPoolSize最大線程數(shù)量都是1,代表該線程有且只有一個(gè)固定的線程,既然是單線程,所以該線程池實(shí)現(xiàn)的是串行操作,沒(méi)有并發(fā)效果。workQueue是LinkedBlockingQueue,這是一個(gè)鏈表阻塞隊(duì)列。所以該線程池適合執(zhí)行串行執(zhí)行隊(duì)列中的任務(wù)。
適合場(chǎng)景:按順序串行處理的任務(wù)。
可能讀者會(huì)好奇keepAliveTime為0代表的含義? 是立即回收線程還是永不回收呢?
keepAliveTime參數(shù)注釋明確指明只對(duì)非核心線程有用。 我們可以從ScheduledThreadPool的源碼中推測(cè),如果0代表是永不回收的話,那么ScheduledThreadPool一旦創(chuàng)建出非核心線程的話就不會(huì)回收了?這樣是很不合理的。所以筆者認(rèn)為0代表立即回收。
public ScheduledThreadPoolExecutor(int corePoolSize) { super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue()); }
此文章如有錯(cuò)誤懇請(qǐng)?jiān)u論指正。希望能通過(guò)這篇文章讓大家對(duì)線程池有更深的理解。
最后,需要關(guān)于分布式,微服務(wù),性能優(yōu)化,Spring,MyBatis,MySQL數(shù)據(jù)庫(kù)等等的源碼知識(shí)點(diǎn)請(qǐng)關(guān)注我哦!還有各種常見面試題詳解。(關(guān)注+轉(zhuǎn)發(fā),私信我“學(xué)習(xí)”,就可以領(lǐng)取架構(gòu)資料了哦!)
我是小架,祝大家工作上能夠事業(yè)有成,學(xué)習(xí)上也可以打破瓶頸一路向上!