另一種類型的線程池是“緩存的”。這樣的線程池和固定線程池一樣常用。

顧名思義,這種線程池就是緩存線程。它使未使用的線程在有限的時間內保持活動狀態,以便重用這些線程來執行新任務。當我們有一些合理的輕量級工作時,這樣的線程池是最好的。

“一些合理的數量”的含義相當廣泛,但你應該知道,這樣的池並不適合每一個任務數量。例如,假設我們要創建一百萬個任務。即使每個都花費很少的時間,我們仍然會使用不合理的資源量並降低性能。當執行時間不可預測時,例如 I/O 任務,我們也應該避免使用此類池。

在幕後,使用以下參數調用ThreadPoolExecutor構造函數:


public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, 
      new SynchronousQueue<Runnable>());
}

以下值作為參數傳遞給構造函數:

範圍 價值
corePoolSize (執行器服務啟動時將準備好(啟動)多少個線程) 0
maximumPoolSize (一個executor服務可以創建的最大線程數) 整數.MAX_VALUE
keepAliveTime(如果線程數大於corePoolSize ,則釋放的線程在被銷毀之前將繼續存在的時間) 60L
單位(時間單位) 時間單位.SECONDS
workQueue(隊列的實現) 新同步隊列<Runnable>()

我們可以用完全相同的方式傳遞我們自己的ThreadFactory實現。

說說同步隊列

同步傳輸的基本思想非常簡單但違反直覺(即直覺或常識告訴您這是錯誤的):當且僅當另一個線程在同時。也就是說,同步隊列裡面不能有任務,因為只要有新的任務到來,執行線程就已經拿走了任務

當一個新任務進入隊列時,如果池中有一個空閒的活動線程,那麼它就會選擇這個任務。如果所有線程都忙,則創建一個新線程。

緩存池從零線程開始,並可能增長到Integer.MAX_VALUE線程。本質上,緩存線程池的大小僅受系統資源的限制。

為了節省系統資源,緩存線程池會刪除空閒一分鐘的線程。

讓我們看看它在實踐中是如何工作的。我們將創建一個模擬用戶請求的任務類:


public class Task implements Runnable {
   int taskNumber;

   public Task(int taskNumber) {
       this.taskNumber = taskNumber;
   }

   @Override
   public void run() {
       System.out.println("Processed user request #" + taskNumber + " on thread " + Thread.currentThread().getName());
   }
}
    

main方法中,我們創建了newCachedThreadPool,然後添加了 3 個任務來執行。這裡我們打印我們服務的狀態(1)

接下來,我們暫停 30 秒,開始另一個任務,並顯示狀態(2)

之後,我們將主線程暫停 70 秒,打印狀態(3),然後再次添加 3 個任務,並再次打印狀態(4)

在我們添加任務後立即顯示狀態的地方,我們首先添加一個 1 秒的睡眠以獲取最新的輸出。


ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 3; i++) {
            executorService.submit(new Task(i));
        }
 
        TimeUnit.SECONDS.sleep(1);
            System.out.println(executorService);	//(1)
 
        TimeUnit.SECONDS.sleep(30);
 
        executorService.submit(new Task(3));
        TimeUnit.SECONDS.sleep(1);
            System.out.println(executorService);	//(2)
 
        TimeUnit.SECONDS.sleep(70);
 
            System.out.println(executorService);	//(3)
 
        for (int i = 4; i < 7; i++) {
            executorService.submit(new Task(i));
        }
 
        TimeUnit.SECONDS.sleep(1);
            System.out.println(executorService);	//(4)
        executorService.shutdown();

結果如下:

在 pool-1-thread-1 線程上處理的用戶請求 #0
在 pool-1-thread-2 線程上
處理的用戶請求 #1 在 pool-1-thread-3 線程上處理的用戶請求 #2
(1) java.util.concurrent .ThreadPoolExecutor@f6f4d33[Running, pool size = 3, active threads = 0, queued tasks = 0, completed tasks = 3]
pool-1-thread-2 thread
(2) java.util.concurrent 上處理的用戶請求 #3。 ThreadPoolExecutor@f6f4d33[正在運行,池大小 = 3,活動線程 = 0,排隊任務 = 0,已完成任務 = 4] (3)
java.util.concurrent.ThreadPoolExecutor@f6f4d33[正在運行,池大小 = 0,活動線程 = 0 , 排隊任務 = 0, 完成任務 = 4]
在 pool-1-thread-4 線程上處理的用戶請求 #4
在 pool-1-thread-5 線程上處理的用戶請求 #5
在 pool-1-thread-4 線程
(4) java.util.concurrent.ThreadPoolExecutor@f6f4d33[Running, pool size = 2, active threads = 0, queued tasks = 0, completed tasks = 7] 上處理的用戶請求 #6

讓我們回顧一下每個步驟:

解釋
1(完成 3 個任務後) 我們創建了3個線程,在這三個線程上執行了3個任務。
當顯示狀態時,3 個任務都已完成,線程準備好執行其他任務。
2(暫停 30 秒並執行另一項任務後) 閒置 30 秒後,線程仍處於活動狀態並等待任務。
添加另一個任務並在從剩餘活動線程池中取出的線程上執行。
沒有新線程被添加到池中。
3(70 秒暫停後) 線程已從池中刪除。
沒有準備好接受任務的線程。
4(再執行 3 個任務後) 收到更多任務後,將創建新線程。這次只有兩個線程設法處理了 3 個任務。

好了,現在您已經熟悉了另一種執行程序服務的邏輯。

與Executors實用程序類的其他方法類比,newCachedThreadPool方法也有一個以ThreadFactory對像作為參數的重載版本。