CountDownLatch
和 CyclicBarrier
是 Java 并发工具包 java.util.concurrent
中提供的同步工具类,用于协调多个线程之间的执行。它们在不同的场景下发挥作用,帮助实现复杂的线程同步需求。下面是它们的内部原理、用法和示例代码。
CountDownLatch
定义
CountDownLatch
是一个同步辅助类,允许一个或多个线程等待,直到其他线程执行的一组操作完成。CountDownLatch
使用一个计数器来实现这一功能,计数器的初始值在创建时设置,每次调用 countDown()
方法时计数器减一,当计数器值为零时,所有等待的线程将被释放。
内部原理
-
初始化:
- 创建
CountDownLatch
时,指定一个初始计数值count
。
- 创建
-
等待:
- 调用
await()
方法的线程会被阻塞,直到计数器值为零。
- 调用
-
计数器减一:
- 调用
countDown()
方法时,计数器减一。
- 调用
-
释放等待线程:
- 当计数器值减到零时,所有等待的线程被释放,继续执行。
-
不可重用:
CountDownLatch
一旦计数器值为零,就不能重置,如果需要重用,可以考虑使用CyclicBarrier
。
用法
- 等待多个线程完成:一个线程等待其他多个线程完成某些操作后再继续执行。
- 初始化阶段:多个线程进行初始化操作,主线程等待所有初始化完成后继续执行。
示例代码
import java.util.concurrent.CountDownLatch;
public class CountDownLatchExample {
public static void main(String[] args) throws InterruptedException {
int numberOfThreads = 3;
CountDownLatch latch = new CountDownLatch(numberOfThreads);
for (int i = 0; i < numberOfThreads; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " is working...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println(Thread.currentThread().getName() + " is done.");
latch.countDown();
}).start();
}
System.out.println("Main thread is waiting for other threads to finish...");
latch.await();
System.out.println("All threads are done. Main thread continues.");
}
}
CyclicBarrier
定义
CyclicBarrier
是一个同步辅助类,允许一组线程互相等待,直到所有线程都到达一个屏障点(barrier point)。CyclicBarrier
可以重用,即在所有线程通过屏障点后,屏障可以被重置并重新使用。
内部原理
-
初始化:
- 创建
CyclicBarrier
时,指定一个参与线程的数量parties
。
- 创建
-
等待:
- 调用
await()
方法的线程会被阻塞,直到所有参与线程都调用await()
方法。
- 调用
-
屏障点:
- 当所有线程都调用
await()
方法时,所有线程被释放,继续执行。
- 当所有线程都调用
-
可重用:
CyclicBarrier
可以重用,所有线程通过屏障点后,屏障可以被重置并重新使用。
-
可选的屏障操作:
- 可以在创建
CyclicBarrier
时指定一个Runnable
任务,在所有线程通过屏障点后执行。
- 可以在创建
用法
- 分阶段执行:多个线程分阶段执行任务,每个阶段完成后所有线程才能继续执行下一阶段。
- 同步点:多个线程在某个同步点等待,直到所有线程都到达该点后再继续执行。
示例代码
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierExample {
public static void main(String[] args) {
int numberOfThreads = 3;
CyclicBarrier barrier = new CyclicBarrier(numberOfThreads, () -> {
System.out.println("All threads have reached the barrier. Barrier action is executed.");
});
for (int i = 0; i < numberOfThreads; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " is working...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println(Thread.currentThread().getName() + " is waiting at the barrier.");
try {
barrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
Thread.currentThread().interrupt();
}
System.out.println(Thread.currentThread().getName() + " is done.");
}).start();
}
}
}
CountDownLatch 和 CyclicBarrier 的比较
特性 | CountDownLatch | CyclicBarrier |
---|---|---|
定义 | 允许一个或多个线程等待其他线程完成一组操作 | 允许一组线程互相等待,直到所有线程都到达屏障点 |
计数器 | 使用一个计数器,计数器减到零时释放所有等待线程 | 使用一个固定数量的参与线程,所有线程到达屏障点时释放 |
重用性 | 一旦计数器为零,不能重用 | 可以重用,所有线程通过屏障点后屏障可以被重置 |
等待操作 | 调用 await() 方法等待计数器为零 | 调用 await() 方法等待所有线程到达屏障点 |
屏障操作 | 无内置的屏障操作 | 可以指定一个 Runnable 任务在所有线程通过屏障点后执行 |
适用场景 | 一个线程等待其他多个线程完成某些操作后再继续执行 | 多个线程分阶段执行任务,每个阶段完成后所有线程才能继续执行 |
应用场景
-
CountDownLatch:
- 初始化阶段:多个线程进行初始化操作,主线程等待所有初始化完成后继续执行。
- 等待多个线程完成:一个线程等待其他多个线程完成某些操作后再继续执行。
-
CyclicBarrier:
- 分阶段执行:多个线程分阶段执行任务,每个阶段完成后所有线程才能继续执行下一阶段。
- 同步点:多个线程在某个同步点等待,直到所有线程都到达该点后再继续执行。
注意事项
-
CountDownLatch:
- 不可重用:一旦计数器为零,
CountDownLatch
不能重用,如果需要重用,可以考虑使用CyclicBarrier
。 - 中断处理:
await()
方法可以被中断,需要处理InterruptedException
。
- 不可重用:一旦计数器为零,
-
CyclicBarrier:
- 重用性:
CyclicBarrier
可以重用,但需要注意所有线程都通过屏障点后屏障会被重置。 - 中断处理:
await()
方法可以被中断,需要处理InterruptedException
和BrokenBarrierException
。 - 屏障操作:屏障操作在所有线程通过屏障点后执行,如果屏障操作抛出异常,所有等待的线程会收到
BrokenBarrierException
。
- 重用性:
总结
-
CountDownLatch:
- 用于一个线程等待其他多个线程完成一组操作后再继续执行。
- 使用一个计数器,计数器减到零时释放所有等待线程。
- 一旦计数器为零,不能重用。
-
CyclicBarrier:
- 用于一组线程互相等待,直到所有线程都到达屏障点后再继续执行。
- 使用一个固定数量的参与线程,所有线程到达屏障点时释放。
- 可以重用,所有线程通过屏障点后屏障可以被重置。
- 可以指定一个屏障操作在所有线程通过屏障点后执行。
理解 CountDownLatch
和 CyclicBarrier
的内部原理和用法有助于设计高效的并发程序,确保线程之间的正确同步。通过合理选择和使用这些同步工具,可以提高程序的性能和可靠性。