请比较 CountDownLatch 类与 CyclicBarrier 类的区别和联系。
参考回答**
CountDownLatch
和 CyclicBarrier
都是 Java 并发包 (java.util.concurrent
) 提供的线程同步工具类,它们的主要作用是协调多个线程的执行顺序。但两者的设计目的和使用方式有所不同。
特性 | CountDownLatch | CyclicBarrier |
---|---|---|
作用 | 一个线程等待多个线程完成后再继续执行。 | 多个线程相互等待,直到所有线程都到达屏障后再继续执行。 |
重用性 | 一次性 使用,计数器不能重置。 | 可重用,屏障可以在所有线程到达后重置并重新使用。 |
触发条件 | 计数器通过 countDown() 逐步减为 0。 |
所有线程调用 await() 并到达屏障。 |
线程数量控制 | 计数器值由开发者设置,线程数量可以动态变化,与任务数量无关。 | 线程数固定,线程总数由 CyclicBarrier 的构造参数决定。 |
主要用途 | 任务分解与汇总,例如主线程等待子线程完成任务。 | 线程阶段性同步,例如并发任务分阶段执行。 |
可选回调机制 | 无。 | 提供 屏障操作(Runnable ),在所有线程到达屏障时触发。 |
详细讲解与比较
1. CountDownLatch 的工作原理与使用
- 原理:
CountDownLatch
通过一个计数器控制线程的同步,计数器的初始值由构造函数指定。- 每个线程在完成任务后调用
countDown()
方法,使计数器减 1。 - 主线程调用
await()
,一直等待直到计数器减为 0,然后继续执行。
- 适用场景:
- 一个线程等待多个线程完成任务后再执行。
- 控制操作顺序,比如主线程在所有依赖任务完成后开始执行。
示例:任务分解与汇总
import java.util.concurrent.CountDownLatch;
public class CountDownLatchExample {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " completed task.");
latch.countDown(); // 计数器减 1
}).start();
}
latch.await(); // 等待计数器减为 0
System.out.println("All tasks completed. Main thread continues.");
}
}
输出示例:
Thread-0 completed task.
Thread-1 completed task.
Thread-2 completed task.
All tasks completed. Main thread continues.
2. CyclicBarrier 的工作原理与使用
- 原理:
CyclicBarrier
是一个循环屏障,线程通过调用await()
方法进入等待状态。- 当所有线程都调用
await()
并到达屏障时,屏障打开,所有线程继续执行。 - 支持重用:屏障会自动重置,可在下一轮任务中再次使用。
- 适用场景:
- 多线程协同工作时,需要线程在某些阶段同步。
- 例如并发任务分阶段执行,每个阶段所有线程需要同步后再开始下一阶段。
示例:多线程分阶段同步
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierExample {
public static void main(String[] args) {
int threadCount = 3;
CyclicBarrier barrier = new CyclicBarrier(threadCount, () -> {
System.out.println("All threads reached the barrier. Proceeding to next phase.");
});
for (int i = 0; i < threadCount; i++) {
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + " reached the barrier.");
barrier.await(); // 等待其他线程到达屏障
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
输出示例:
Thread-0 reached the barrier.
Thread-1 reached the barrier.
Thread-2 reached the barrier.
All threads reached the barrier. Proceeding to next phase.
3. CountDownLatch 与 CyclicBarrier 的主要区别
特性 | CountDownLatch | CyclicBarrier |
---|---|---|
是否可重用 | 一次性使用,计数器不能重置。 | 可重用,屏障在所有线程到达后自动重置。 |
线程等待的条件 | 主线程通过 await() 等待计数器减为 0。 |
所有线程通过 await() 等待其他线程到达屏障。 |
使用场景 | 一个线程等待多个线程完成任务。 | 多个线程相互等待,分阶段协作。 |
是否有回调机制 | 无。 | 支持屏障操作(Runnable 回调),所有线程到达屏障时触发。 |
线程数量的灵活性 | 与任务数量一致,线程数量可以动态变化。 | 固定线程数量,由 CyclicBarrier 构造时指定。 |
4. 使用场景对比
场景 | 推荐工具 | 原因 |
---|---|---|
主线程等待多个子线程完成任务 | CountDownLatch |
主线程通过 await() 等待所有子线程完成任务后继续执行。 |
多线程分阶段同步执行 | CyclicBarrier |
多线程需要在每个阶段同步后再继续执行下一阶段。 |
单次任务调度 | CountDownLatch |
计数器一次性使用,适合单次任务调度。 |
可重复使用的屏障 | CyclicBarrier |
屏障可重用,适合需要多轮任务协作的场景。 |
5. 联系与补充
- 实现原理:
- 两者都基于 AQS(AbstractQueuedSynchronizer) 实现线程同步。
CountDownLatch
使用共享模式来减少计数器。CyclicBarrier
使用独占模式实现线程屏障。
- 组合使用:
CountDownLatch
和CyclicBarrier
可以结合使用,例如主线程等待多个阶段的执行结果。
总结
工具类 | 特点 |
---|---|
CountDownLatch | – 主线程等待多个子线程完成任务。- 一次性使用,计数器不可重置。 |
CyclicBarrier | – 多个线程相互等待,分阶段协作。- 支持回调和重用,适合循环使用。 |
在实际开发中,应根据任务的需求选择合适的工具:
- 使用
CountDownLatch
适合一次性任务汇总。 - 使用
CyclicBarrier
适合需要多阶段、循环同步的场景。