史上最全執行緒池超詳解(建議收藏)

碼農談IT發表於2023-01-09

執行緒池是併發程式設計的必備技術,下面我詳細介紹執行緒的原理、執行流程、以及詳細的建立方式


本篇內容大綱:

  1. 執行緒池的由來

  2. 執行緒池的優點和風險

  3. 執行緒池的原理和實現

  4. 執行緒池大小的配置

  5. 執行緒池的四種實現


一、執行緒池的由來


我們有兩種常見的建立執行緒的方法,一種是繼承Thread類,一種是實現Runnable的介面,Thread類其實也是實現了Runnable介面。但是我們建立這兩種執行緒在執行結束後都會被虛擬機器銷燬,如果執行緒數量多的話,頻繁的建立和銷燬執行緒會大大浪費時間和效率,更重要的是浪費記憶體。那麼有沒有一種方法能讓執行緒執行完後不立即銷燬,而是讓執行緒重複使用,繼續執行其他的任務哪?


這就是執行緒池的由來,很好的解決執行緒的重複利用,避免重複開銷。



二、執行緒池的優點


1、執行緒是稀缺資源,使用執行緒池可以減少建立和銷燬執行緒的次數,每個工作執行緒都可以重複使用。

2、可以根據系統的承受能力,調整執行緒池中工作執行緒的數量,防止因為消耗過多記憶體導致伺服器崩潰。



三、執行緒池的風險


雖然執行緒池是構建多執行緒應用程式的強大機制,但使用它並不是沒有風險的。


用執行緒池構建的應用程式容易遭受任何其它多執行緒應用程式容易遭受的所有併發風險,諸如同步錯誤和死鎖,它還容易遭受特定於執行緒池的少數其它風險,諸如與池有關的死鎖、資源不足和執行緒洩漏。


1.死鎖


任何多執行緒應用程式都有死鎖風險。


當一組程式或執行緒中的每一個都在等待一個只有該組中另一個程式才能引起的事件時,我們就說這組程式或執行緒 死鎖了。


死鎖的最簡單情形是:執行緒 A 持有物件 X 的獨佔鎖,並且在等待物件 Y 的鎖,而執行緒 B 持有物件 Y 的獨佔鎖,卻在等待物件 X 的鎖。除非有某種方法來打破對鎖的等待(Java 鎖定不支援這種方法),否則死鎖的執行緒將永遠等下去。


2.資源不足


執行緒池的一個優點在於:相對於其它替代排程機制而言,它們通常執行得很好,但只有恰當地調整了執行緒池大小時才是這樣的。


執行緒消耗包括記憶體和其它系統資源在內的大量資源


除了 Thread 物件所需的記憶體之外,每個執行緒都需要兩個可能很大的執行呼叫堆疊。除此以外,JVM 可能會為每個 Java 執行緒建立一個本機執行緒,這些本機執行緒將消耗額外的系統資源。


最後,雖然執行緒之間切換的排程開銷很小,但如果有很多執行緒,環境切換也可能嚴重地影響程式的效能。


如果執行緒池太大,那麼被那些執行緒消耗的資源可能嚴重地影響系統效能。線上程之間進行切換將會浪費時間,而且使用超出比您實際需要的執行緒可能會引起資源匱乏問題,因為池執行緒正在消耗一些資源,而這些資源可能會被其它任務更有效地利用。


3.執行緒洩漏


各種型別的執行緒池中一個嚴重的風險是執行緒洩漏,當從池中除去一個執行緒以執行一項任務,而在任務完成後該執行緒卻沒有返回池時,會發生這種情況。發生執行緒洩漏的一種情形出現在任務丟擲一個 RuntimeException 或一個 Error 時。


如果池類沒有捕捉到它們,那麼執行緒只會退出而執行緒池的大小將會永久減少一個。當這種情況發生的次數足夠多時,執行緒池最終就為空,而且系統將停止,因為沒有可用的執行緒來處理任務。


4.請求過載


僅僅是請求就壓垮了伺服器,這種情況是可能的。在這種情形下,我們可能不想將每個到來的請求都排隊到我們的工作佇列,因為排在佇列中等待執行的任務可能會消耗太多的系統資源並引起資源缺乏。在這種情形下決定如何做取決於您自己;在某些情況下,您可以簡單地拋棄請求,依靠更高階別的協議稍後重試請求,您也可以用一個指出伺服器暫時很忙的響應來拒絕請求。



四、執行緒池的實現原理


史上最全執行緒池超詳解(建議收藏)


1.執行緒池狀態


執行緒池和執行緒一樣擁有自己的狀態,在ThreadPoolExecutor類中定義了一個volatile變數runState來表示執行緒池的狀態,執行緒池有四種狀態,分別為RUNNING、SHURDOWN、STOP、TERMINATED。

1)執行緒池建立後處於RUNNING狀態。

2)呼叫shutdown後處於SHUTDOWN狀態,執行緒池不能接受新的任務,會等待緩衝佇列的任務完成。

3)呼叫shutdownNow後處於STOP狀態,執行緒池不能接受新的任務,並嘗試終止正在執行的任務。

4)當執行緒池處於SHUTDOWN或STOP狀態,並且所有工作執行緒已經銷燬,任務快取佇列已經清空或執行結束後,執行緒池被設定為TERMINATED狀態。

執行緒池原理:預先啟動一些執行緒,執行緒無限迴圈從任務佇列中獲取一個任務進行執行,直到執行緒池被關閉。如果某個執行緒因為執行某個任務發生異常而終止,那麼重新建立一個新的執行緒而已,如此反覆。


2.執行緒池的處理流程


1)判斷執行緒池裡的核心執行緒是否都在執行任務,如果不是(核心執行緒空閒或者還有核心執行緒沒有被建立)則建立一個新的工作執行緒來執行任務。如果核心執行緒都在執行任務,則進入下個流程。


2)執行緒池判斷工作佇列是否已滿,如果工作佇列沒有滿,則將新提交的任務儲存在這個工作佇列裡。如果工作佇列滿了,則進入下個流程。


3)判斷執行緒池裡的執行緒是否都處於工作狀態,如果沒有,則建立一個新的工作執行緒來執行任務。如果已經滿了,則交給飽和策略來處理這個任務。


史上最全執行緒池超詳解(建議收藏)



五、執行緒池的配置大小


一般需要根據任務的型別來配置執行緒池大小:

  1. 如果是CPU密集型任務,就需要儘量壓榨CPU,參考值可以設為 NCPU+1

  2. 如果是IO密集型任務,參考值可以設定為2*NCPU


當然,這只是一個參考值,具體的設定還需要根據實際情況進行調整,比如可以先將執行緒池大小設定為參考值,再觀察任務執行情況和系統負載、資源利用率來進行適當調整。



六、Java執行緒池的四種實現


  1. newCachedThreadPool建立一個可快取執行緒池,如果執行緒池長度超過處理需要,可靈活回收空閒執行緒,若無可回收,則新建執行緒。

  2. newFixedThreadPool 建立一個定長執行緒池,可控制執行緒最大併發數,超出的執行緒會在佇列中等待。

  3. newScheduledThreadPool 建立一個定長執行緒池,支援定時及週期性任務執行。

  4. newSingleThreadExecutor 建立一個單執行緒化的執行緒池,它只會用唯一的工作執行緒來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先順序)執行。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70024924/viewspace-2931352/,如需轉載,請註明出處,否則將追究法律責任。

相關文章