Java執行緒池newCachedThreadPool()與newFixedThreadPool()區別 | Baeldung

banq發表於2020-03-11

當涉及執行緒池實現時,Java標準庫提供了很多選擇。在這些實現中,固定執行緒池和快取執行緒池非常普遍。

快取執行緒池newCachedThreadPool

Executors.newCachedThreadPool快取執行緒池是從零個執行緒開始,並且可能會增長為具有  Integer.MAX_VALUE個 執行緒。實際上,快取執行緒池的唯一限制是可用的系統資源。

為了更好地管理系統資源,快取的執行緒池將刪除閒置一分鐘的執行緒。為了更好地管理系統資源,快取的執行緒池將刪除閒置一分鐘的執行緒。

假設有一個新任務進入。如果佇列中有一個空閒執行緒正在等待,則任務生產者將任務移交給該執行緒。否則,由於佇列始終已滿,執行程式將建立一個新執行緒來處理該任務。

快取執行緒池配置會在很短的時間內快取執行緒(因此命名),以將其重用於其他任務。因此,當我們處理相當數量的短期任務時,它最有效。 

這裡的關鍵是“合理”和“短暫”。為了澄清這一點,讓我們評估一個快取池不太適合的情況。在這裡,我們將提交100萬個任務,每個任務需要100微秒的時間來完成:

allable<String> task = () -> {
    long oneHundredMicroSeconds = 100_000;
    long startedAt = System.nanoTime();
    while (System.nanoTime() - startedAt <= oneHundredMicroSeconds);
 
    return "Done";
};
 
var cachedPool = Executors.newCachedThreadPool();
var tasks = IntStream.rangeClosed(1, 1_000_000).mapToObj(i -> task).collect(toList());
var result = cachedPool.invokeAll(tasks);

這將建立許多執行緒,這些執行緒會轉換為不合理的記憶體使用,甚至更糟的是,還會有許多CPU上下文切換。這兩個異常都會嚴重損害整體效能。

因此,當執行時間不可預測時(如IO繫結任務),我們應避免使用快取執行緒池。

固定執行緒池newFixedThreadPool

與快取執行緒池相反,該執行緒正在使用具有固定數量的永不過期執行緒的無界佇列。因此,固定執行緒池不是使用數量不斷增加的執行緒,而是嘗試使用固定數量的執行緒執行傳入的任務。當所有執行緒都忙時,執行程式將對新任務進行排隊。這樣,我們可以更好地控制程式的資源消耗。

因此,固定執行緒池更適合執行時間無法預測的任務。

共同點

除了所有這些差異,它們都使用  AbortPolicy 作為飽和策略。因此,我們希望這些執行器在無法接受甚至無法排隊任務時丟擲異常。

讓我們看看現實世界中發生了什麼。

在極端情況下,快取執行緒池將繼續建立越來越多的執行緒,因此,實際上,它們永遠不會達到飽和點。同樣,固定執行緒池將繼續在其佇列中新增越來越多的任務。因此,固定池也永遠不會達到飽和點。

由於兩個池都不會飽和,因此當負載異常高時,它們將消耗大量記憶體來建立執行緒或排隊任務。更糟的是,快取的執行緒池也將導致很多處理器上下文切換。

無論如何,為了更好地控制資源消耗,強烈建議建立一個自定義  ThreadPoolExecutor

var boundedQueue = new ArrayBlockingQueue<Runnable>(1000);
new ThreadPoolExecutor(10, 20, 60, SECONDS, boundedQueue, new AbortPolicy());

在這裡,我們的執行緒池最多可以有20個執行緒,最多隻能排隊1000個任務。同樣,當它不能再接受任何負載時,它只會丟擲一個異常。

 

相關文章