[Java併發]執行緒的並行等待

Duancf發表於2024-09-28

在 Java 中,執行緒的並行等待(即等待多個執行緒並行執行完畢)通常可以透過以下幾種方式來實現。我們要確保多個執行緒能夠並行執行,並在所有執行緒執行完畢後再繼續後續的操作。這種場景通常可以用在併發任務的協調中。以下是幾種常見的實現方式:

1. 使用 Thread.join() 方法

join() 方法是最直接的方式,用於等待一個執行緒執行完畢。為了實現並行等待多個執行緒的完成,可以對每個執行緒分別呼叫 join(),主執行緒會依次等待每個子執行緒執行結束。

示例:

public class ParallelWaitExample {
    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(() -> {
            System.out.println("Thread 1 started");
            try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
            System.out.println("Thread 1 finished");
        });

        Thread thread2 = new Thread(() -> {
            System.out.println("Thread 2 started");
            try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); }
            System.out.println("Thread 2 finished");
        });

        thread1.start();
        thread2.start();

        // 主執行緒等待兩個執行緒完成
        thread1.join(); // 等待 thread1 完成
        thread2.join(); // 等待 thread2 完成

        System.out.println("All threads finished");
    }
}

解釋:在這個例子中,主執行緒依次呼叫 thread1.join()thread2.join(),確保在兩個執行緒都執行完畢後繼續執行後續邏輯。

2. 使用 CountDownLatch

CountDownLatch 是一個同步工具類,可以用來協調多個執行緒的執行順序。透過它,主執行緒可以等待一組執行緒執行完畢。

使用步驟:

  • 建立 CountDownLatch,初始值為執行緒數。
  • 每個執行緒在任務完成後呼叫 countDown(),表示自己完成任務。
  • 主執行緒呼叫 await() 方法,等待計數器歸零(即所有執行緒完成任務)。

示例:

import java.util.concurrent.CountDownLatch;

public class CountDownLatchExample {
    public static void main(String[] args) throws InterruptedException {
        int numThreads = 2;
        CountDownLatch latch = new CountDownLatch(numThreads);

        Thread thread1 = new Thread(() -> {
            System.out.println("Thread 1 started");
            try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
            System.out.println("Thread 1 finished");
            latch.countDown(); // 任務完成,計數器減1
        });

        Thread thread2 = new Thread(() -> {
            System.out.println("Thread 2 started");
            try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); }
            System.out.println("Thread 2 finished");
            latch.countDown(); // 任務完成,計數器減1
        });

        thread1.start();
        thread2.start();

        latch.await(); // 主執行緒等待所有執行緒完成
        System.out.println("All threads finished");
    }
}

解釋CountDownLatch 的計數器初始值為 2,表示等待兩個執行緒完成。每個執行緒執行完畢後呼叫 countDown(),主執行緒呼叫 await() 阻塞,直到所有執行緒完成任務。

3. 使用 CyclicBarrier

CyclicBarrier 是一個同步輔助工具,用來讓一組執行緒等待彼此到達一個共同的屏障點。當指定數量的執行緒都呼叫了 await() 方法時,所有執行緒才會繼續執行。

示例:

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierExample {
    public static void main(String[] args) {
        int numThreads = 2;
        CyclicBarrier barrier = new CyclicBarrier(numThreads, () -> {
            // 所有執行緒都到達屏障後執行該任務
            System.out.println("All threads finished");
        });

        Thread thread1 = new Thread(() -> {
            System.out.println("Thread 1 started");
            try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
            System.out.println("Thread 1 finished");
            try { barrier.await(); } catch (InterruptedException | BrokenBarrierException e) { e.printStackTrace(); }
        });

        Thread thread2 = new Thread(() -> {
            System.out.println("Thread 2 started");
            try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); }
            System.out.println("Thread 2 finished");
            try { barrier.await(); } catch (InterruptedException | BrokenBarrierException e) { e.printStackTrace(); }
        });

        thread1.start();
        thread2.start();
    }
}

解釋CyclicBarrier 的構造引數是 2,表示兩個執行緒都需要到達屏障(呼叫 await())後才能繼續執行。所有執行緒到達後,屏障會觸發並執行定義的任務。

4. 使用 ExecutorServiceFuture

ExecutorService 提供了一種管理執行緒池的方式,它可以提交一組任務並使用 Future 來等待它們的完成。

示例:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.Callable;
import java.util.ArrayList;
import java.util.List;

public class ExecutorServiceExample {
    public static void main(String[] args) throws Exception {
        ExecutorService executor = Executors.newFixedThreadPool(2);

        List<Callable<String>> tasks = new ArrayList<>();
        tasks.add(() -> {
            System.out.println("Task 1 started");
            Thread.sleep(1000);
            System.out.println("Task 1 finished");
            return "Result 1";
        });
        tasks.add(() -> {
            System.out.println("Task 2 started");
            Thread.sleep(2000);
            System.out.println("Task 2 finished");
            return "Result 2";
        });

        // 提交任務並獲取 Future 列表
        List<Future<String>> results = executor.invokeAll(tasks);

        for (Future<String> result : results) {
            System.out.println("Task result: " + result.get()); // 等待任務完成並獲取結果
        }

        executor.shutdown();
    }
}

解釋:使用 ExecutorService 建立一個執行緒池,透過 invokeAll() 提交任務,主執行緒會阻塞直到所有任務執行完畢,然後可以透過 Future.get() 獲取每個任務的結果。

總結

  1. Thread.join():簡單、直接地等待每個執行緒完成,但需要逐個呼叫。
  2. CountDownLatch:適合在多個執行緒完成任務後觸發主執行緒的後續操作。
  3. CyclicBarrier:適合需要多執行緒相互等待的場景。
  4. ExecutorServiceFuture:執行緒池管理,並行執行任務並等待所有任務完成,適用於多工並行執行場景。

你可以根據應用的具體需求選擇合適的並行等待機制。

相關文章