在Java并发编程中,确保多个线程能够按照预定的顺序或条件协同工作是一项重要而复杂的任务。CountDownLatch
和CyclicBarrier
是Java并发包(java.util.concurrent
)中提供的两个非常有用的工具类,它们各自以不同的方式帮助开发者实现线程间的同步,确保线程能够“步调一致”地执行。本章将深入探讨这两个类的使用场景、工作原理、以及它们如何帮助我们在多线程环境中控制线程的执行顺序。
CountDownLatch
是一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。CountDownLatch
的工作原理类似于倒计时器:初始化时设置一个计数器(count),每当一个线程完成了它的任务,计数器的值就减一(通过调用countDown()
方法)。当计数器的值达到零时,所有等待的线程(通过调用await()
方法)将被唤醒继续执行。
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CountDownLatchExample {
public static void main(String[] args) throws InterruptedException {
int numThreads = 5;
CountDownLatch latch = new CountDownLatch(numThreads);
ExecutorService executor = Executors.newFixedThreadPool(numThreads);
for (int i = 0; i < numThreads; i++) {
executor.submit(() -> {
try {
// 模拟任务执行
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
latch.countDown(); // 任务完成,计数器减一
});
}
latch.await(); // 等待所有任务完成
System.out.println("所有任务完成");
executor.shutdown();
}
}
与CountDownLatch
不同,CyclicBarrier
允许一组线程相互等待,直到达到某个公共屏障点(common barrier point)。所有线程都必须调用await()
方法,然后这些线程将在屏障处被阻塞,直到最后一个线程到达。当最后一个线程到达屏障时,所有线程将被释放,继续执行它们的await()
调用之后的代码。值得注意的是,CyclicBarrier
是可以重用的,即一旦所有线程都被释放,它可以被重置并再次使用,无需重新创建新的对象。
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierExample {
public static void main(String[] args) {
int numThreads = 4;
CyclicBarrier barrier = new CyclicBarrier(numThreads, () -> {
System.out.println("所有线程都到达了屏障点,继续执行后续任务");
});
for (int i = 0; i < numThreads; i++) {
new Thread(() -> {
try {
// 模拟任务执行
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " 到达屏障点");
barrier.await(); // 等待其他线程
} catch (InterruptedException | BrokenBarrierException e) {
Thread.currentThread().interrupt();
}
}).start();
}
}
}
CountDownLatch
主要用于一个或多个线程等待其他多个线程完成某项操作;而CyclicBarrier
则是让一组线程相互等待,直到它们都达到某个公共屏障点。CountDownLatch
是不可重用的,一旦计数器到达零,就不能再次被使用;而CyclicBarrier
是可以重用的,每次所有线程通过屏障后,它都可以被重置并再次使用。CountDownLatch
的触发条件是计数器达到零;而CyclicBarrier
的触发条件是达到屏障点的线程数量与构造时指定的线程数量一致。CountDownLatch
和CyclicBarrier
时,应确保它们的使用不会导致线程永久阻塞或不必要的等待。CyclicBarrier
时,如果其中一个线程因异常退出,其他线程将抛出BrokenBarrierException
。因此,在调用await()
方法时,应准备好处理此异常。await()
方法时,如果线程被中断,它将抛出InterruptedException
。在捕获此异常后,通常需要将中断状态重新设置到当前线程,以便上层调用者能够感知到中断。通过合理使用CountDownLatch
和CyclicBarrier
,我们可以有效地控制多线程的执行顺序,实现复杂的并发逻辑。然而,这也要求开发者对Java并发编程有深入的理解,能够准确地把握同步机制的使用时机和方式,以避免潜在的问题。