線程池
線程池是一種多線程處理形式,處理過程中將任務(wù)添加到隊列,然后在創(chuàng)建線程后自動啟動這些任務(wù)。
線程池是幫助我們處理以及管理多線程的。若是沒有線程池,則每當(dāng)有任務(wù)到達(dá)時都會新建一個線程來自處理,若任務(wù)特別多的時候,就會無休止的創(chuàng)建很多線程,這不但會消耗系統(tǒng)的資源,還會降低整個系統(tǒng)的穩(wěn)定性。
然而有了線程池,則會通過對已創(chuàng)建線程的重復(fù)利用,降低創(chuàng)建和銷毀線程時的資源消耗。同時,若是有任務(wù)到達(dá),可以復(fù)用之前創(chuàng)建的線程不需要再去創(chuàng)建新的線程。同時可以規(guī)定線程池的大小,并對線程進(jìn)行調(diào)優(yōu)和監(jiān)控,降低對整個系統(tǒng)的影響。
線程池的7個參數(shù)
這七個參數(shù)分別是:核心線程數(shù),最大線程數(shù),最大空閑時間,時間單位,阻塞隊列,線程工廠,拒絕策略
我們結(jié)合當(dāng)下的一個例子聊聊這七個參數(shù)
假如我們有一個公司,有幾個核心員工,也有幾個外包員工,有招人的hr,也有個項目經(jīng)理。 員工干活,hr招人,項目經(jīng)理接任務(wù)。
核心線程數(shù)corePoolSize
就是核心員工,干活滴,不會被開除。這個公司最少會有這么多人在干活。
核心線程數(shù)就是這個線程池最低保持的線程數(shù)量
最大線程數(shù)maximumPoolSize
這是所有員工,老板給定的最大人數(shù),核心員工+外包的數(shù)量,最多也就這么多人。
也就是這個線程池所能容納的最多的線程數(shù)量。當(dāng)設(shè)定這個值的時候,若是小于核心線程數(shù),會拋出非法參數(shù)的錯。
最大空閑時間keepAliveTime
這就有意思了,當(dāng)外包人員沒活了,在空閑這么多時間之后,就會給嘎掉,告辭了各位。
也就是說,非核心線程在這么多時間沒有執(zhí)行任務(wù)之后會被關(guān)閉。
時間單位unit
這就是上邊最大空閑時間的單位,外包人員看到個10,以為10天,暗自慶幸,還有時間找找新的,結(jié)果,10min,哈哈,10分鐘后走人。
阻塞隊列workQueue
項目經(jīng)理,活全在他這里放著,干活的從這里領(lǐng)活然后去處理。
線程池存放任務(wù)的隊列,用來存儲線程池的所有待執(zhí)行任務(wù)。
線程工廠threadFactory
這就是個hr,線程池創(chuàng)建之后,有任務(wù)來了,先招聘核心員工,當(dāng)核心員工忙不過來的時候,再招聘外包員工。
線程池創(chuàng)建線程時調(diào)用的工廠方法。
拒絕策略handler
當(dāng)公司里每個人都有活干,這里包括核心員工和外包員工,并且項目經(jīng)理那里也已經(jīng)堆滿了的時候,再有任務(wù)來的時候的處理方式
當(dāng)下有四種處理方式
- AbortPolicy:拒絕并拋出異常。項目經(jīng)理拒絕了新的任務(wù),并且向上級領(lǐng)導(dǎo)上報了此事。整個公司停擺了。也就是,老子干不了了,直接掀桌子。默認(rèn)就是這個拒絕策略。
- CallerRunsPolicy:使用當(dāng)前調(diào)用的線程來執(zhí)行此任務(wù)。誰提的任務(wù),誰處理。當(dāng)一個mAIn線程將任務(wù)拋給線程池的時候,線程池滿了,并且是這個拒絕策略,則會讓這個main線程去執(zhí)行這個任務(wù)。也就是異步任務(wù)變成同步任務(wù)了。這個要慎用,等于阻塞了用戶的請求,整體服務(wù)會變慢。
- DiscardOldestPolicy:拋棄隊列頭部(最舊)的一個任務(wù),并執(zhí)行當(dāng)前任務(wù)。項目經(jīng)理放棄隊列里最開始的一個任務(wù),并將新來的任務(wù)放在最后一個。一般也不用,或者說根據(jù)實際情況看能否使用的到。
- DiscardPolicy:忽略并拋棄當(dāng)前任務(wù)。誰都不接受,來的新任務(wù)都忽略掉。
這個在我們環(huán)境引起過一個bug,我們使用的DiscardPolicy這個拒絕策略,導(dǎo)入8萬個用戶資料的時候,五個線程處理,阻塞隊列的大小只有5萬,這樣,導(dǎo)入的時候只會有5萬個用戶被導(dǎo)入成功,其余的用戶不見了,找了許久才發(fā)現(xiàn)問題在這里。也沒改策略,吧導(dǎo)入的文件拆成了兩個,分別導(dǎo)入了。
也可以自定義一個拒絕策略,實現(xiàn)RejectedExecutionHandle接口,并重寫rejectedeExecution方法即可

線程池的狀態(tài)
線程池有五個狀態(tài)
- RUNNING 在這個狀態(tài)下,有任務(wù)就正常接收并處理,線程池剛創(chuàng)建出來的默認(rèn)狀態(tài)就是這個
- SHUTDOWN 這個狀態(tài)下不會接受新的任務(wù),但會處理正在執(zhí)行的任務(wù)和工作隊列中的任務(wù),當(dāng)線程池執(zhí)行shutdown()方法后,處于這個狀態(tài)。
- STOP 不會接受新的任務(wù),會立即中斷正在執(zhí)行的線程,并且不會處理工作隊列中的任務(wù)當(dāng)線程池執(zhí)行當(dāng)線程池執(zhí)行shutdownNow()方法之后處于這個狀態(tài)
- TIDYING SHUTDOWN的工作隊列中的任務(wù)都處理完了,SHUTDOWN和STOP工作線程都關(guān)閉之后的過渡狀態(tài),線程池馬上涼涼,是個過渡狀態(tài)。
- TERMINATED 死亡,執(zhí)行terminated()之后的狀態(tài)。
那么這里為啥要一個TIDYING這個過渡狀態(tài)呢?是為了確保SHUTDOWN和STOP狀態(tài)中的工作線程都已經(jīng)關(guān)閉。在這個狀態(tài)下執(zhí)行了執(zhí)行terminated()方法之后這才會到TERMINATED狀態(tài)。也就是說我們可以重寫這個方法,來處理一些線程池關(guān)閉之后的事情。
并且線程池的狀態(tài)是在ctl屬性中記錄的。本質(zhì)是個int類型,ctl的高三位記錄線程池狀態(tài)
在低29位,才會記錄工作線程個數(shù)。也就是說即便指定的線程最大數(shù)量是Integer.MAX_VALUE他也到不了,一個線程池的最多線程也就是2的29次方個了。
線程池執(zhí)行流程
核心線程不是new完就構(gòu)建的,是懶加載的機(jī)制,添加任務(wù)才會構(gòu)建核心線程,具體流程看下圖:

這里我們可以看到很重要的一點(diǎn),當(dāng)任務(wù)隊列滿的時候,才會創(chuàng)建非核心線程。
當(dāng)沒有任務(wù)的時候,核心線程在干什么?
線程會掛起,默認(rèn)核心線程是WAITING狀態(tài),非核心是TIMED_WAITING
如果是核心線程,默認(rèn)情況下,會在阻塞隊列的位置執(zhí)行take()方法,直到拿到任務(wù)為止。就是說,一直等,有活我就干,沒活就一直等。
如果是非核心線程,默認(rèn)情況下,會在阻塞隊列的位置執(zhí)行poll()方法,等待最大空閑時間,如果沒任務(wù),直接嘎掉,如果有活,那就正常干。