CyclicBarrier
和 CountDownLatch
是 Java 并发包(java.util.concurrent
)中的两个常用同步工具,它们用于协调多个线程的执行。然而,它们在设计和使用方式上有着显著的区别,特别是在可重复使用方面。
1. CyclicBarrier 是可重复使用的
CyclicBarrier
是一个循环的屏障(cyclic),其主要功能是让一组线程到达某个屏障点后再同时继续执行。当所有线程都到达屏障时,它会自动重置并进入下一轮等待,这就是它“可重复使用”的特性。它的重用性源于每次线程到达屏障后,会自动重置计数器,并允许再次使用。
-
特点:
- 复用性:一旦所有线程都到达屏障并通过,屏障自动重置,允许这些线程或其他线程再次使用该屏障进行同步。这意味着
CyclicBarrier
可以在多个循环或阶段中重复使用。 - 栅栏动作:
CyclicBarrier
还允许在线程到达屏障时执行一个栅栏动作(可选),该动作可以是某个特定的任务,如日志记录、数据汇总等。
- 复用性:一旦所有线程都到达屏障并通过,屏障自动重置,允许这些线程或其他线程再次使用该屏障进行同步。这意味着
-
示例:
CyclicBarrier barrier = new CyclicBarrier(3); // 3个线程需要到达屏障 Runnable task = () -> { try { System.out.println(Thread.currentThread().getName() + " 到达屏障"); barrier.await(); // 等待其他线程到达屏障 System.out.println(Thread.currentThread().getName() + " 通过屏障"); } catch (Exception e) { e.printStackTrace(); } }; for (int i = 0; i < 3; i++) { new Thread(task).start(); } // CyclicBarrier 可以重复使用,允许继续同步新的线程到达屏障 // 运行结果 Thread-2 到达屏障 Thread-1 到达屏障 Thread-0 到达屏障 Thread-1 通过屏障 Thread-2 通过屏障 Thread-0 通过屏障
这里,当所有 3 个线程都到达
barrier.await()
时,屏障将被释放,线程继续执行。在下一轮中,这些线程可以再次使用同一个CyclicBarrier
。
2. CountDownLatch 是一次性的
CountDownLatch
是一个倒计时锁存器,它用于让一个或多个线程等待,直到其他线程完成某个操作。当计数器到达 0 时,所有等待的线程被释放,之后 CountDownLatch
就无法再次使用了。
-
特点:
- 一次性使用:
CountDownLatch
是一次性的。一旦计数器从初始值倒数到 0,锁存器不能重置,也不能重新计数。如果需要再次使用,只能创建一个新的CountDownLatch
实例。 - 没有栅栏动作:
CountDownLatch
不像CyclicBarrier
那样有一个统一的屏障动作,它只是一个简单的计数器,控制多个线程的同步。
- 一次性使用:
-
示例:
CountDownLatch latch = new CountDownLatch(3); // 初始计数为 3 Runnable task = () -> { System.out.println(Thread.currentThread().getName() + " 完成任务"); latch.countDown(); // 每个线程完成任务后调用 countDown() }; for (int i = 0; i < 3; i++) { new Thread(task).start(); } try { latch.await(); // 等待计数器变为 0 System.out.println("所有任务完成,继续执行主线程"); } catch (InterruptedException e) { e.printStackTrace(); } // 运行结果: Thread-2 完成任务 Thread-1 完成任务 Thread-0 完成任务 所有任务完成,继续执行主线程
在这个例子中,
CountDownLatch
初始计数为 3,表示需要 3 个线程完成任务。当计数器变为 0 时,主线程继续执行。然而,CountDownLatch
在计数变为 0 后无法重置,不能再次使用。
3. 为什么 CyclicBarrier
可重复使用,而 CountDownLatch
是一次性的?
-
设计目的:
CyclicBarrier
:设计的目的是在循环或阶段性任务中使用,它允许同一批线程在不同的阶段都同步等待,完成一轮后可以自动重置并继续下一轮,因此它是循环使用的工具。CountDownLatch
:设计目的是让某个线程等待一组操作完成后继续执行,常用于一次性事件(如系统初始化、任务完成的信号)。它的计数一旦减为 0,表示操作已经完成,之后不能重新计数,因此是一次性的工具。
-
实现机制:
CyclicBarrier
:每当线程到达屏障并通过时,屏障自动重置,允许再次使用。其内部维护一个计数器和栅栏代(generation)来实现多次复用。CountDownLatch
:计数器一旦减到 0,锁存器就完成任务,无法重置,除非重新创建一个新的实例。
4. 使用场景的差异
-
CyclicBarrier
使用场景:- 多线程分阶段处理任务时,每个阶段都需要所有线程在某个点上同步。例如,在一个并行算法中,多个线程需要在每个阶段结束时进行同步。
-
CountDownLatch
使用场景:- 在某些初始化操作中,主线程需要等待其他线程完成后继续执行。例如,一个主线程等待几个工作线程完成任务,然后才继续执行。
总结
CyclicBarrier
可重复使用:它的设计允许在多个循环或阶段中重复使用,当一组线程到达屏障后,屏障会自动重置,进入下一轮等待。CountDownLatch
是一次性的:它的计数器一旦到达 0,就无法再次使用,只能用于一次性任务协调。如果需要再次使用,必须创建新的实例。