首頁 > 軟體

java 執行緒池的實現原理、優點與風險、以及4種執行緒池實現

2023-02-22 06:00:53

為什麼需要執行緒池

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

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

執行緒池的優點

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

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

執行緒池的風險

雖然執行緒池是構建多執行緒應用程式的強大機制,但使用它並不是沒有風險的。用執行緒池構建的應用程式容易遭受任何其它多執行緒應用程式容易遭受的所有並行風險,諸如同步錯誤和死鎖,它還容易遭受特定於執行緒池的少數其它風險,諸如與池有關的死鎖、資源不足和執行緒洩漏。

1.死鎖

任何多執行緒應用程式都有死鎖風險。當一組程序或執行緒中的每一個都在等待一個只有該組中另一個程序才能引起的事件時,我們就說這組程序或執行緒 死鎖了。死鎖的最簡單情形是:執行緒 A 持有物件 X 的獨佔鎖,並且在等待物件 Y 的鎖,而執行緒 B 持有物件 Y 的獨佔鎖,卻在等待物件 X 的鎖。除非有某種方法來打破對鎖的等待(Java 鎖定不支援這種方法),否則死鎖的執行緒將永遠等下去。

2.資源不足

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

執行緒消耗包括記憶體和其它系統資源在內的大量資源。除了
Thread 物件所需的記憶體之外,每個執行緒都需要兩個可能很大的執行呼叫堆疊。除此以外,JVM 可能會為每個 Java
執行緒建立一個本機執行緒,這些本機執行緒將消耗額外的系統資源。最後,雖然執行緒之間切換的排程開銷很小,但如果有很多執行緒,環境切換也可能嚴重地影響程式的效能。

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

除了執行緒自身所使用的資源以外,服務請求時所做的工作可能需要其它資源,例如 JDBC 連線、通訊端或檔案,這些也都是有限資源,有太多的並行請求也可能引起失效,例如不能分配 JDBC 連線。

3.並行錯誤

執行緒池和其它排隊機制依靠使用
wait() 和 notify()
方法,這兩個方法都難於使用。如果編碼不正確,那麼可能丟失通知,導致執行緒保持空閒狀態,儘管佇列中有工作要處理。使用這些方法時,必須格外小心;即便是專家也可能在它們上面出錯。而最好使用現有的、已經知道能工作的實現,例如在
util.concurrent 包。

4.執行緒洩漏

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

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

5.請求過載

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

執行緒池的實現原理

執行緒池

1.執行緒池狀態

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

  •  執行緒池建立後處於RUNNING狀態。
  •  呼叫shutdown後處於SHUTDOWN狀態,執行緒池不能接受新的任務,會等待緩衝佇列的任務完成。
  •  呼叫shutdownNow後處於STOP狀態,執行緒池不能接受新的任務,並嘗試終止正在執行的任務。
  •  當執行緒池處於SHUTDOWN或STOP狀態,並且所有工作執行緒已經銷燬,任務快取佇列已經清空或執行結束後,執行緒池被設定為TERMINATED狀態。

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

2.執行緒池的處理流程

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

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

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

設定執行緒池大小設定

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

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

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

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

Java提供的四種執行緒池實現

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

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

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

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

以上就是java 執行緒池的實現原理、優點與風險、以及4種執行緒池實現的詳細內容,更多關於java 執行緒池的實現原理的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com