1.使用ExecutorService
和CountDownLatch
的方法示例
在Java中,當我們使用執行緒池(如ExecutorService
)來執行非同步任務時,常常需要知道所有任務是否都已經完成。ExecutorService
介面提供了幾種方式來處理這種情況,但最常用的是shutdown()
和awaitTermination()
方法的組合,或者使用Future
和CompletionService
。這裡我將提供一個使用ExecutorService
和CountDownLatch
的示例,因為CountDownLatch
提供了一種直觀的方式來等待一組執行緒完成。
首先,我們定義幾個任務,然後使用ExecutorService
來非同步執行它們,並使用CountDownLatch
來等待所有任務完成。
import java.util.concurrent.*;
public class ThreadPoolExample {
public static void main(String[] args) throws InterruptedException {
// 建立一個包含固定數量執行緒的執行緒池
ExecutorService executorService = Executors.newFixedThreadPool(4);
// 定義任務數量
int taskCount = 10;
// 使用CountDownLatch來等待所有任務完成
final CountDownLatch latch = new CountDownLatch(taskCount);
// 提交任務到執行緒池
for (int i = 0; i < taskCount; i++) {
int taskId = i;
executorService.submit(() -> {
// 模擬任務執行
try {
Thread.sleep(1000); // 假設每個任務需要1秒
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("任務 " + taskId + " 完成");
// 每完成一個任務,計數減一
latch.countDown();
});
}
// 等待所有任務完成
System.out.println("等待所有任務完成...");
latch.await(); // 阻塞當前執行緒,直到latch的計數達到零
System.out.println("所有任務完成!");
// 關閉執行緒池
executorService.shutdown();
// 可選:等待執行緒池中的執行緒都執行完畢
try {
if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
// 執行緒池沒有在規定時間內關閉,則強制關閉
executorService.shutdownNow();
}
} catch (InterruptedException e) {
// 當前執行緒在等待過程中被中斷
executorService.shutdownNow();
Thread.currentThread().interrupt();
}
}
}
在這個例子中,我們首先建立了一個固定大小的執行緒池(這裡使用4個執行緒)。然後,我們定義了一個CountDownLatch
,其計數被初始化為任務的數量(這裡為10)。對於每個任務,我們都向執行緒池提交了一個Runnable
,其中包含了任務的執行邏輯和latch.countDown()
呼叫,以確保每次任務完成時都會減少CountDownLatch
的計數。
主執行緒透過呼叫latch.await()
來等待,直到所有任務都呼叫了countDown()
(即計數達到零),然後才能繼續執行。這確保了主執行緒會等待所有任務完成後再繼續。
最後,我們關閉了執行緒池,並透過呼叫awaitTermination()
來可選地等待執行緒池中的所有執行緒都執行完畢。如果執行緒池沒有在指定時間內關閉,則呼叫shutdownNow()
來嘗試立即停止所有正在執行的任務。
這個示例提供了處理非同步任務並等待它們完成的一種有效方式,適用於需要等待所有任務完成再繼續的場景。
2.使用ExecutorService
的invokeAll
方法和Future
列表的方法示例
除了使用CountDownLatch
之外,還有其他方法可以判斷執行緒池中的所有任務是否執行完成。以下是一個使用ExecutorService
的invokeAll
方法和Future
列表的示例,這種方法適用於我們有一組已知的任務(Callable
)需要並行執行,並且我們需要等待所有任務完成並獲取它們的結果。
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
public class ThreadPoolFutureExample {
public static void main(String[] args) throws InterruptedException, ExecutionException {
// 建立一個包含固定數量執行緒的執行緒池
ExecutorService executorService = Executors.newFixedThreadPool(4);
// 建立一個Callable任務列表
List<Callable<String>> tasks = new ArrayList<>();
for (int i = 0; i < 10; i++) {
final int taskId = i;
tasks.add(() -> {
// 模擬任務執行
Thread.sleep(1000); // 假設每個任務需要1秒
return "任務 " + taskId + " 完成";
});
}
// 使用invokeAll提交所有任務,這將返回一個Future列表
List<Future<String>> futures = executorService.invokeAll(tasks);
// 遍歷Future列表,獲取每個任務的結果
for (Future<String> future : futures) {
// get()會阻塞,直到對應的任務完成
System.out.println(future.get());
}
// 關閉執行緒池
executorService.shutdown();
// 可選:等待執行緒池中的執行緒都執行完畢
try {
if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
// 執行緒池沒有在規定時間內關閉,則強制關閉
executorService.shutdownNow();
}
} catch (InterruptedException e) {
// 當前執行緒在等待過程中被中斷
executorService.shutdownNow();
Thread.currentThread().interrupt();
}
}
}
// 注意:這裡使用了Lambda表示式和方法引用來簡化Callable的建立
// 實際使用中,你可能需要實現Callable介面或使用匿名內部類
在這個例子中,我們建立了一個ExecutorService
和一個Callable
任務列表。每個Callable
任務都會返回一個字串,表示任務完成的資訊。我們使用invokeAll
方法提交了所有任務,並立即獲得了一個Future
列表,每個Future
都代表了一個任務的執行結果。
然後,我們遍歷這個Future
列表,並對每個Future
呼叫get()
方法。get()
方法會阻塞當前執行緒,直到對應的任務完成並返回結果。這樣,我們就能確保在繼續執行之前,所有任務都已經完成。
最後,我們關閉了執行緒池,並等待所有執行緒都執行完畢(或超時後強制關閉)。
請注意,雖然這個示例使用了Callable
和Future
,但它並沒有直接提供一個“是否所有任務都已完成”的布林值。然而,透過遍歷Future
列表並呼叫get()
,我們實際上已經達到了等待所有任務完成的效果。如果我們只需要知道是否所有任務都已開始執行(而不是等待它們完成),那麼我們可能需要採用不同的策略,比如使用execute
方法結合其他同步機制(如CountDownLatch
)。
3.使用ExecutorService
來非同步執行多個Callable
任務方法示例
以下是一個詳細完整的程式碼示例,該示例使用了ExecutorService
來非同步執行多個Callable
任務,並透過遍歷Future
列表來等待所有任務完成並獲取它們的結果。
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
public class ThreadPoolFutureCompleteExample {
public static void main(String[] args) {
// 建立一個包含固定數量執行緒的執行緒池
ExecutorService executorService = Executors.newFixedThreadPool(4);
// 建立一個Callable任務列表
List<Callable<String>> tasks = new ArrayList<>();
for (int i = 0; i < 10; i++) {
final int taskId = i;
tasks.add(new Callable<String>() {
@Override
public String call() throws Exception {
// 模擬任務執行
TimeUnit.SECONDS.sleep(1); // 假設每個任務需要1秒
return "任務 " + taskId + " 完成";
}
});
// 或者使用Lambda表示式(如果你使用的是Java 8或更高版本)
// tasks.add(() -> {
// TimeUnit.SECONDS.sleep(1);
// return "任務 " + taskId + " 完成";
// });
}
try {
// 使用invokeAll提交所有任務,這將返回一個Future列表
List<Future<String>> futures = executorService.invokeAll(tasks);
// 遍歷Future列表,獲取每個任務的結果
for (Future<String> future : futures) {
// get()會阻塞,直到對應的任務完成
System.out.println(future.get());
}
// 關閉執行緒池
executorService.shutdown();
// 等待執行緒池中的所有執行緒都執行完畢(可選)
// 注意:由於我們已經呼叫了invokeAll並等待了所有Future的完成,這一步通常是多餘的
// 但為了完整性,我還是展示瞭如何等待執行緒池關閉
boolean terminated = executorService.awaitTermination(60, TimeUnit.SECONDS);
if (!terminated) {
// 如果執行緒池沒有在規定時間內關閉,則強制關閉
System.err.println("執行緒池沒有在規定時間內關閉,嘗試強制關閉...");
executorService.shutdownNow();
// 注意:shutdownNow()不保證已經提交的任務會被取消
// 它會嘗試停止正在執行的任務,但已經開始執行的任務可能無法被中斷
}
} catch (InterruptedException | ExecutionException e) {
// 處理異常
e.printStackTrace();
// 如果當前執行緒在等待過程中被中斷,嘗試關閉執行緒池
if (!executorService.isShutdown()) {
executorService.shutdownNow();
}
// 根據需要,可能還需要重新設定中斷狀態
Thread.currentThread().interrupt();
}
}
}
在這個示例中,我使用了傳統的匿名內部類來建立Callable
任務(同時也提供了Lambda表示式的註釋),以便與各種Java版本相容。然而,如果我們正在使用Java 8或更高版本,我強烈推薦我們使用Lambda表示式來簡化程式碼。
請注意,invokeAll
方法會阻塞呼叫它的執行緒,直到所有任務都完成,或者直到等待超時(如果我們提供了超時時間)。但是,在這個示例中,我們沒有為invokeAll
提供超時時間,因此它會一直等待,直到所有任務都完成。
另外,請注意,在catch
塊中,如果捕獲到InterruptedException
,我們檢查了執行緒池是否已經被關閉(使用isShutdown
方法)。如果沒有,我們呼叫shutdownNow
方法來嘗試關閉執行緒池並停止正在執行的任務。然而,需要注意的是,shutdownNow
方法並不保證能夠停止所有已經開始執行的任務,因為某些任務可能無法被中斷。
最後,如果在捕獲到InterruptedException
後,我們確定當前執行緒需要被重新中斷(比如,我們在一個迴圈中等待某個條件,而中斷是用來退出迴圈的),那麼我們應該呼叫Thread.currentThread().interrupt()
來重新設定中斷狀態。在這個示例中,我們沒有這樣做,因為main
方法不需要重新中斷。但是,在更復雜的場景中,這可能是必要的。