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

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

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