如何建立一個執行緒池,為什麼不推薦使用Executors去建立呢?

JavaBuild發表於2024-06-06

我們在學執行緒的時候瞭解了幾種建立執行緒的方式,比如繼承Thread類,實現Runnable介面、Callable介面等,那對於執行緒池的使用,也需要去建立它,在這裡我們提供2種構造執行緒池的方法:

方法一: 透過ThreadPoolExecutor建構函式來建立(首選)
   這是JDK中最核心的執行緒池工具類,在JDK1.8中,它提供了豐富的可設定的執行緒池構造引數,供我們設計不同的執行緒池,如下:
image

透過構造方法 ,可以給整個執行緒池設定大小、等待佇列、非核心執行緒存活時間、建立執行緒的工廠類、拒絕策略等,具體引數描述可見 第六問,它們線上程池中所對應的關係,可見下圖。

image

方法二: 透過 Executor 框架的工具類 Executors 來建立(不推薦)
   Executors 是java併發工具包中的一個靜態工廠類,在JDK1.5時被創造出來,提供了豐富的創造執行緒池的方法,透過它可以建立多種型別的執行緒池。

image

  • newFixedThreadPool:建立定長執行緒池,該執行緒池中的執行緒數量始終不變。當有一個新的任務提交時,執行緒池中若有空閒執行緒,則立即執行。若沒有,則新的任務會被暫存在一個任務佇列中,待有執行緒空閒時,便處理在任務佇列中的任務。當執行緒發生錯誤結束時,執行緒池會補充一個新的執行緒;
  • newCachedThreadPool:建立可快取的執行緒池,如果執行緒池的容量超過了任務數,自動回收空閒執行緒,任務增加時可以自動新增新執行緒,所有執行緒在當前任務執行完畢後,將返回執行緒池進行復用,執行緒池的容量不限制;
  • newScheduledThreadPool:建立定長執行緒池,可執行週期性的任務;
  • newSingleThreadExecutor:建立單執行緒的執行緒池,只有一個執行緒的執行緒池。若多餘一個任務被提交到該執行緒池,任務會被儲存在一個任務佇列中,待執行緒空閒,按先入先出的順序執行佇列中的任務,執行緒異常結束,會建立一個新的執行緒,能確保任務按提交順序執行;
  • newWorkStealingPool:任務可竊取執行緒池,不保證執行順序,當有空閒執行緒時會從其他任務佇列竊取任務執行,適合任務耗時差異較大的場景。

為何很多大廠都禁止使用Executors 建立執行緒池呢?

   如果大家跟入到Executors這些方法的底層實現中去看一眼的話,立馬就知道原因了,像FixedThreadPool 和 SingleThreadExecutor這兩個方法內使用的是無界的 LinkedBlockingQueue儲存任務,任務佇列最大長度為 Integer.MAX_VALUE,這樣可能會堆積大量的請求,從而導致 OOM。

   而CachedThreadPool使用的是同步佇列 SynchronousQueue, 允許建立的執行緒數量也為 Integer.MAX_VALUE ,如果任務數量過多且執行速度較慢,可能會建立大量的執行緒,從而導致 OOM,其他的方法所提供的均是這種無界任務佇列,在高併發場景下導致OOM的風險很大,故大部分的公司已經不建議採用Executors提供的方法建立執行緒池了。

相關文章