1. 程式人生 > >java執行緒池建立方式

java執行緒池建立方式

當前環境

  1. jdk == 1.8

Executors 使用的隱患

先來看一段程式碼,我們要建立一個固定執行緒池,假設固定執行緒數是4。程式碼如下:

Executors是JAVA併發包中提供的,用來快速建立不同型別的執行緒池。

是不是很簡單,建立執行緒池只需一行程式碼。對於一些個人專案或臨時性的專案,這樣寫確實沒什麼問題,而且開發速度很快。但在一些大型專案中,這種做法一般是禁止的。

WHY???

因為用Executors建立的執行緒池存在效能隱患,我們看一下原始碼就知道,用Executors建立執行緒池時,使用的佇列是new LinkedBlockingQueue<Runnable>(),這是一個無邊界佇列,如果不斷的往裡加任務時,最終會導致記憶體問題,也就是說在專案中由於使用了無邊界佇列,導致的記憶體佔用的不可控性。下圖是不斷新增執行緒任務導致老年代被佔滿的情況:

當然,除了記憶體問題,它還存在一些其他的問題,在下面對執行緒池引數的介紹中會具體說明。

執行緒池的正確建立方式

其實,問題很好解決。提供的簡便方式有侷限性,那我們自己new一個ThreadPoolExecutor,無非多寫幾行程式碼而已。

關於ThreadPoolExecutor的具體程式碼如下:

引數說明:

  • corePoolSize:核心執行緒數;
  • maximumPoolSize:最大執行緒數,即執行緒池中允許存在的最大執行緒數;
  • keepAliveTime:執行緒存活時間,對於超過核心執行緒數的執行緒,當執行緒處理空閒狀態下,且維持時間達到keepAliveTime時,執行緒將被銷燬;
  • unit:keepAliveTime的時間單位
  • workQueue:工作佇列,用於存在待執行的執行緒任務;
  • threadFactory:建立執行緒的工廠,用於標記區分不同執行緒池所創建出來的執行緒;
  • handler:當到達執行緒數上限或工作佇列已滿時的拒絕處理邏輯;

具體程式碼

  • 自定義threadFactory。除了可以自定義建立的執行緒名稱,方便問題排查,在newThread(Runnable r)建立執行緒的方法中,還可以進行定製化設定,如為執行緒設定特定上下文等。

  • 自定義RejectedExecutionHandler。記錄異常資訊,選擇不同處理邏輯,有交由當前執行緒執行任務,有直接丟擲異常,再或者等待後繼續新增任務等。

  • 建立自定義執行緒池

執行緒池內在處理邏輯

我們通過一些例子,來觀察一下其內部的處理邏輯。基於上述具體程式碼,我們已經建立了一個核心執行緒數4,最大執行緒數8,執行緒存活時間10s,工作佇列最大容量為10的一個執行緒池。

  • 初始化執行緒池:未新增執行緒任務

    • 這時,執行緒池中***不會建立任何執行緒***,存活執行緒為0,工作佇列為0.
  • 未達核心執行緒數:新增4個執行緒任務

    • 由於當前存活執行緒數 <= 核心執行緒數,所以會***建立新的執行緒***。即存活執行緒為4,工作佇列為0.
  • 核心執行緒數已滿:新增第5個執行緒任務

    • 若當前執行緒池中存在空閒執行緒,則交由該執行緒處理。即存活執行緒為4,工作佇列為0.
    • 若當前所有執行緒處理執行狀態,加入工作佇列。即存活執行緒為4,工作佇列為1.(注意:此時工作佇列中的任務不會被執行,直到有執行緒空閒後,才能被處理
  • 工作佇列未滿:假設新增的任務都是耗時操作(短時間不會結束),再新增9個耗時任務

    • 即存活執行緒為4,工作佇列為10.
  • 工作佇列已滿 & 未達最大執行緒數:再新增4個任務

    • 當工作佇列已滿,且不存在空閒執行緒,此時會***建立額外執行緒***來處理當前任務。此時存活執行緒為8,工作佇列為10.
  • 工作佇列已滿 & 且最大執行緒數已滿:再新增1個任務

    • 觸發RejectedExecutionHandler,將當前任務交由自己設定的執行控制代碼進行處理。此時存活執行緒為8,工作佇列為10.
  • 當任務執行完後,沒有新增的任務,臨時擴充的執行緒(大於核心執行緒數的)將在10s(keepAliveTime)後被銷燬。

總結

最後,我們在使用執行緒池的時候,需要根據使用場景來自行選擇。通過corePoolSize和maximumPoolSize的搭配,存活時間的選擇,以及改變佇列的實現方式,如:選擇延遲佇列,來實現定時任務的功能。併發包Executors中提供的一些方法確實好用,但我們仍需有保留地去使用,這樣在專案中就不會挖太多的坑。

擴充套件

對於一些耗時的IO任務,盲目選擇執行緒池往往不是最佳方案。通過非同步+單執行緒輪詢,上層再配合上一個固定的執行緒池,效果可能更好。類似與Reactor模型中selector輪詢處理。

文章轉自:http://www.iteye.com/news/32893#comments