Executors類的newFixedThreadPool方法創建了一個固定線程數的executorService 。與newSingleThreadExecutor方法不同,我們指定池中需要多少個線程。在引擎蓋下,調用以下代碼:
new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue());
corePoolSize (執行器服務啟動時準備好(啟動)的線程數)和maximumPoolSize(執行器服務可以創建的最大線程數)參數接收相同的值——傳遞給newFixedThreadPool(nThreads)的線程數)。我們可以用完全相同的方式傳遞我們自己的ThreadFactory實現。
好吧,讓我們看看為什麼我們需要這樣一個ExecutorService。
這是具有固定數量 (n) 線程的ExecutorService的邏輯:
- 最多有 n 個線程將處於活動狀態以處理任務。
- 如果提交的任務超過 n 個,它們將被保留在隊列中,直到線程空閒為止。
- 如果其中一個線程失敗並終止,將創建一個新線程來代替它。
- 池中的任何線程都處於活動狀態,直到池關閉。
例如,想像一下在機場等待通過安檢。每個人都排成一排,直到安檢前,乘客被分配到所有工作檢查站。如果其中一個檢查點出現延遲,則隊列將僅由第二個檢查點處理,直到第一個檢查點空閒為止。如果一個檢查站完全關閉,那麼另一個檢查站將被打開以取代它,乘客將繼續通過兩個檢查站進行處理。
我們會立即註意到,即使條件是理想的——承諾的 n 個線程穩定工作,並且以錯誤結束的線程總是被替換(在真實的機場中有限的資源不可能實現的東西)——系統仍然有幾個不愉快的特性,因為在任何情況下都不會有更多的線程,即使隊列增長的速度快於線程可以處理任務的速度。
我建議實際了解ExecutorService如何使用固定數量的線程。讓我們創建一個實現Runnable 的類。此類的對象代表我們對ExecutorService 的任務。
public class Task implements Runnable {
int taskNumber;
public Task(int taskNumber) {
this.taskNumber = taskNumber;
}
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Processed user request #" + taskNumber + " on thread " + Thread.currentThread().getName());
}
}
在run()方法中,我們阻塞線程 2 秒,模擬一些工作負載,然後顯示當前任務的編號和執行任務的線程的名稱。
ExecutorService executorService = Executors.newFixedThreadPool(3);
for (int i = 0; i < 30; i++) {
executorService.execute(new Task(i));
}
executorService.shutdown();
首先,在main方法中,我們創建一個ExecutorService並提交 30 個任務以供執行。
在 pool-1-thread-1 線程上處理的用戶請求 #0
在 pool-1-thread-3 線程上處理的用戶請求 #2
在 pool- 上處理的用戶請求 #5 1-thread-3 線程
在 pool-1-thread-2 線程上處理的用戶請求 #3
在 pool-1-thread-1 線程上處理的用戶請求 #4 在 pool-1
-thread-1 線程上處理的用戶請求 #8
處理的用戶pool-1-thread-3 線程上的請求 #6 在
pool-1-thread-2 線程上處理的用戶請求 #7 在 pool-1
-thread-3 線程上處理的用戶請求 #10
在 pool-1- 上處理的用戶請求 #9 thread-1 thread
在 pool-1-thread-2 線程上處理的用戶請求 #11
在 pool-1-thread-3 線程上處理的用戶請求 #12
在 pool-1-thread-2 線程上處理的用戶請求 #14 在
pool-1-thread-1 線程上
處理的用戶請求 #13 在 pool-1-thread-3 線程上處理的用戶請求 #15
在 pool- 上處理的用戶請求 #16 1-thread-2 線程
在 pool-1-thread-1 線程上處理的用戶請求 #17
在 pool-1-thread-3 線程
上處理的用戶請求 #18 在 pool-1-thread-2 線程上處理的用戶請求 #19
處理的用戶pool-1-thread-1 線程上的請求 #20 在
pool-1-thread-3 線程上處理的用戶請求 #21
在 pool-1-thread-2 線程上處理的用戶請求 #22
在 pool-1- 上處理的用戶請求 #23 thread-1 thread
在 pool-1-thread-2 線程上處理的用戶請求 #25
在 pool-1-thread-3 線程上處理的用戶請求 #24
在 pool-1-thread-1 線程上處理的用戶請求 #26
在 pool-1-thread-2 線程上
處理的用戶請求 #27 在 pool-1-thread-3 線程上處理的用戶請求 #28
在 pool- 上處理的用戶請求 #29 1-thread-1線程
控制台輸出向我們展示了任務在被前一個任務釋放後如何在不同線程上執行。
現在我們將任務數增加到 100,在提交 100 個任務後,我們將調用 awaitTermination (11, SECONDS)方法。我們傳遞一個數字和時間單位作為參數。此方法將阻塞主線程 11 秒。然後我們將調用shutdownNow()強制關閉ExecutorService ,而不等待所有任務完成。
ExecutorService executorService = Executors.newFixedThreadPool(3);
for (int i = 0; i < 100; i++) {
executorService.execute(new Task(i));
}
executorService.awaitTermination(11, SECONDS);
executorService.shutdownNow();
System.out.println(executorService);
最後,我們將顯示有關executorService狀態的信息。
這是我們得到的控制台輸出:
在 pool-1-thread-3 線程
上處理的用戶請求 #2 在 pool-1-thread-2 線程上處理的用戶請求 #1
在 pool- 上處理的用戶請求 #4 1-thread-3 線程
在 pool-1-thread-2 線程上處理的用戶請求 #5
在 pool-1-thread-1 線程
上處理的用戶請求 #3 在 pool-1-thread-3 線程上處理的用戶請求 #6
處理的用戶pool-1-thread-2 線程上的請求 #7 在 pool-
1-thread-1 線程上處理的用戶請求 #8 在 pool
-1-thread-3 線程上處理的用戶請求 #9
在 pool-1- 上處理的用戶請求 #11 thread-1 thread
在 pool-1-thread-2 線程上處理的用戶請求 #10
在 pool-1-thread-1 線程上處理的用戶請求 #13
在 pool-1-thread-2 線程上處理的用戶請求 #14 在
pool-1-thread-3 線程上處理的用戶請求 #12
java.util.concurrent.ThreadPoolExecutor@452b3a41[正在關閉,池大小 = 3,活動線程 = 3 , 排隊任務 = 0, 完成任務 = 15]
pool-1-thread-3 線程上已處理的用戶請求 #17 在
pool-1-thread-1 線程上已處理的用戶請求 #15 在 pool-
1-thread 上已處理的用戶請求 #16 -2線程
接下來是 3 個InterruptedExceptions ,由 3 個活動任務的睡眠方法拋出。
我們可以看到當程序結束時,15 個任務已經完成,但是池中仍有 3 個活動線程沒有完成它們的任務執行。在這三個線程上調用了interrupt ()方法,這意味著任務將完成,但在我們的例子中,睡眠方法拋出InterruptedException。我們還看到調用shutdownNow()方法後,任務隊列被清空。
所以當使用池中線程數固定的ExecutorService時,一定要記住它是如何工作的。這種類型適用於具有已知恆定負載的任務。
這裡還有一個有趣的問題:如果你需要為單線程使用一個執行器,你應該調用哪個方法?newSingleThreadExecutor()或newFixedThreadPool(1)?
兩個執行者都會有相同的行為。唯一的區別是newSingleThreadExecutor()方法將返回一個執行程序,該執行程序以後無法重新配置為使用其他線程。
GO TO FULL VERSION