聊到線程池相信大家應該都聽過,其實也不排除很多程序員工作了好幾年後沒用過線程池,這個現象不少見。
聊點題外話,IT行業目前還是很火,形形色色的公司都有,所以就有了形形色色的程序員,外包公司慎入!慎入!慎入!
好了言歸正傳.
線程池地方好處:
線程使應用能夠充分合理的協調利用CPU,內存,網絡,I/O等系統資源。創建線程需要開闢虛擬機棧,本地方法棧,程序計數器等私有的內存空間。並且銷燬時又需要回收這些資源,這樣頻繁的創建銷燬線程會浪費大量的系統資源,增加我們併發編程的風險,如果在服務器負載過高的如何讓新的線程等待或者優雅的拒絕服務呢?這些都是線程無法解決的問題,所以呢我們需要一個用來管理這些線程的池子,我們叫它線程池。另外線程池還有以下作用:
1 利用線程池管理並且複用線程,控制最大併發數
2 實現隊列緩存策略以及拒絕策略
3 可以實現特定的功能,比如週期性執行,單個線程,並行執行執行等
4 可以實現環境隔離: 比如搜索和下單如果部署在同一個項目中,開闢兩個線程池可以很好的讓他們的環境隔離,不影響他們 各自的效率
--------------------------------------------
後面寫: 介紹Excutors(線程池工具類:獲取線程池,類似Collections) |-------->ForkJoinPool(類)
|------>AbstractExecutorService--->
Excutor頂級接口--》 ExcutorsService(接口)----> |--------->ThreadPoolExcutor(類)
|-------->ScheduledExcutorService |
|-----> ScheduledThreadPool(定時任務)
介紹下線程池的一些參數:
corePoolSize : 線程池維護線程的最少數量,哪怕是空閒的。注意這個也是可以設置存活時間的
maximumPoolSize :線程池維護線程的最大數量。
keepAliveTime : 線程池維護線程所允許的空閒時間。
unit : 線程池維護線程所允許的空閒時間的單位。
workQueue : 線程池所使用的緩衝隊列,改緩衝隊列的長度決定了能夠緩衝的最大數量。不同的BlockingQueue決定了線程池 的排隊策略。 (下面會介紹)
拒絕任務:拒絕任務是指當線程池裏面的線程數量達到 maximumPoolSize 且 workQueue 隊列已滿的情況下被嘗試添加進來的任務。
handler : 線程池對拒絕任務的處理策略。在 ThreadPoolExecutor 裏面定義了 4 種 handler 策略,分別是
1. CallerRunsPolicy :這個策略重試添加當前的任務,他會自動重複調用 execute() 方法,直到成功。(可能會造成OOM)
2. AbortPolicy :對拒絕任務拋棄處理,並且拋出異常。
3. DiscardPolicy :對拒絕任務直接無聲拋棄,沒有異常信息。
4. DiscardOldestPolicy :對拒絕任務不拋棄,而是拋棄隊列裏面等待最久的一個線程,然後把拒絕任務加到隊列。
一個任務通過 execute(Runnable) 方法被添加到線程池,任務就是一個 Runnable 類型的對象,任務的執行方法就是 Runnable 類型對象的 run() 方法。
當一個任務通過 execute(Runnable) 方法欲添加到線程池時,線程池採用的策略如下:
1. 如果此時線程池中的數量小於 corePoolSize ,即使線程池中的線程都處於空閒狀態,也要創建新的線程來處理被添加的任務。
2. 如果此時線程池中的數量等於 corePoolSize ,但是緩衝隊列 workQueue 未滿,那麼任務被放入緩衝隊列。
3. 如果此時線程池中的數量大於 corePoolSize ,緩衝隊列 workQueue 滿,並且線程池中的數量小於maximumPoolSize ,建新的線程來處理被添加的任務。
4. 如果此時線程池中的數量大於 corePoolSize ,緩衝隊列 workQueue 滿,並且線程池中的數量等於maximumPoolSize ,那麼通過 handler 所指定的拒絕策略來處理此任務。
處理任務的優先級爲:
核心線程 corePoolSize 、任務隊列 workQueue 、最大線程 maximumPoolSize ,如果三者都滿了,使用 handler處理被拒絕的任務。當線程池中的線程數量大於 corePoolSize 時,如果某線程空閒時間超過 keepAliveTime ,線程將被終止。這樣,線程池可以動態的調整池中的線程數。
理解了上面關於 ThreadPoolExecutord 的介紹,應該就基本能瞭解它的一些使用,不過在 ThreadPoolExocutor裏面有個關鍵的 Worker 類,所有的線程都要經過 Worker 的包裝。這樣才能夠做到線程可以複用而無需重新創建線程。
同時 Executors 類裏面有 newFixedThreadPool(),newCachedThreadPool() 等幾個方法,實際上也是間接調用了ThreadPoolExocutor ,不過是傳的不同的構造參數
各線程之間關係圖:
ThreadPoolExecutor執行器中使用的是BlockingQueue阻塞隊列
a)SynchronousQueue 並不是一個真正的隊列,而是一種在線程之間進行移交的機制。要將一個元素放入SynchronousQueue中,必須有另一個線程正在等待接受這個元素。如果沒有線程正在等待,並且線程池的當前大小小於最大值,那麼ThreadPoolExecutor將會創建一個新的線程,否則會根據飽和策略拒絕掉這個任務。
b)LinkedBlockingQueue 基於鏈表的FIFO隊列,默認隊列大小爲Integer.MAX_VALUE,因此是無界隊列。當活躍線程等於corePoolSize時,新添加的任務都會被放入隊列等待,因此maximumPoolSize就無效了。無界隊列可以用來處理瞬時的高併發情況。
爲什麼叫它無界隊列呢? 因爲創建線程池時如果沒有指定隊列大小是使用下面的代碼來初始化隊列
public LinkedBlockingQueue() {
//默認是Integer的最大值
this(Integer.MAX_VALUE);
}
這樣的話就會造成OOM,因爲隊列足夠大,會導致堆內存滿了,並且所有的任務都在等待
c)ArrayBlockingQueue 基於數組的FIFO隊列,創建時必須指定大小。使用有界隊列有助於防止資源耗盡。
其中默認使用LinkedBlockingQeque 無界隊列實現的,既然講到這個那麼我們就來聊一聊
threadFactory:線程工廠,每當線程池需要創建一個線程時,都是通過線程工廠方法來完成的。默認的線程工廠方法將會創建一個新的、非守護的線程,並且不包含特殊的配置信息。在ThreadFactory中只定義了一個方法newThread,每當線程池需要創建一個新線程時都會調用這個方法。通常情況下我們都需要去使用自定義的線程工廠方法,自定義的線程工廠可以提供如下功能:
a)爲線程取一個有意義的名字,便於後續排查問題。
b)爲線程指定UncaughtExceptionHandler來處理線程執行過程中未被捕獲的異常。
c)修改線程優先級或者守護狀態。
所以說線程池的設置: 核心線程數量,最大線程數量,隊列選擇,拒絕策略都需要根據具體的業務場景來選擇,否則可能會導致OOM或者一些意想不到的錯誤。
後續繼續補。。。。。。。。。。