Java非同步判斷執行緒池所有任務是否執行完成的方法

TechSynapse發表於2024-07-11

1.使用ExecutorServiceCountDownLatch的方法示例

在Java中,當我們使用執行緒池(如ExecutorService)來執行非同步任務時,常常需要知道所有任務是否都已經完成。ExecutorService介面提供了幾種方式來處理這種情況,但最常用的是shutdown()awaitTermination()方法的組合,或者使用FutureCompletionService。這裡我將提供一個使用ExecutorServiceCountDownLatch的示例,因為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.使用ExecutorServiceinvokeAll方法和Future列表的方法示例

除了使用CountDownLatch之外,還有其他方法可以判斷執行緒池中的所有任務是否執行完成。以下是一個使用ExecutorServiceinvokeAll方法和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()方法會阻塞當前執行緒,直到對應的任務完成並返回結果。這樣,我們就能確保在繼續執行之前,所有任務都已經完成。

最後,我們關閉了執行緒池,並等待所有執行緒都執行完畢(或超時後強制關閉)。

請注意,雖然這個示例使用了CallableFuture,但它並沒有直接提供一個“是否所有任務都已完成”的布林值。然而,透過遍歷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方法不需要重新中斷。但是,在更復雜的場景中,這可能是必要的。

相關文章