当前位置: 技术文章>> 如何使用 CountDownLatch 和 CyclicBarrier?
文章标题:如何使用 CountDownLatch 和 CyclicBarrier?
在并发编程中,`CountDownLatch` 和 `CyclicBarrier` 是 Java 并发包(`java.util.concurrent`)中两个非常有用的同步工具,它们各自适用于不同的场景,但共同点是都能帮助协调多个线程之间的操作顺序。下面,我将详细解释这两个工具的使用场景、工作原理,并通过实例代码展示如何在实际编程中应用它们。
### CountDownLatch
`CountDownLatch` 是一种同步工具,它允许一个或多个线程等待其他线程完成一组操作后再继续执行。`CountDownLatch` 的工作原理是,在初始化时设置一个计数器(count),任何线程都可以调用 `countDown()` 方法来减少这个计数器的值。当计数器的值减至零时,那些因为调用 `await()` 方法而阻塞的线程将被唤醒,继续执行后续操作。
#### 使用场景
- **并行计算**:当你需要将一个大的计算任务分割成多个小任务,并希望在所有小任务都完成后才进行结果汇总时。
- **资源初始化**:在多个线程需要使用某个资源之前,必须确保该资源已经被完全初始化。
#### 示例代码
假设我们有一个场景,需要同时从多个数据源加载数据,并在所有数据加载完成后才进行汇总处理。
```java
import java.util.concurrent.CountDownLatch;
public class DataLoader {
private final CountDownLatch latch;
public DataLoader(int count) {
this.latch = new CountDownLatch(count);
}
public void loadData(int dataSourceId) {
// 模拟数据加载过程
try {
System.out.println("数据源 " + dataSourceId + " 开始加载数据...");
Thread.sleep(1000); // 假设每个数据源加载数据需要1秒
System.out.println("数据源 " + dataSourceId + " 数据加载完成.");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
latch.countDown(); // 加载完成后,减少计数器
}
}
public void waitForAllDataLoaded() throws InterruptedException {
latch.await(); // 等待所有数据源加载完成
System.out.println("所有数据加载完成,开始汇总处理.");
}
public static void main(String[] args) {
DataLoader loader = new DataLoader(3); // 假设有三个数据源
// 启动三个线程加载数据
for (int i = 1; i <= 3; i++) {
new Thread(() -> loader.loadData(i)).start();
}
try {
loader.waitForAllDataLoaded(); // 等待所有数据加载完成
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
```
### CyclicBarrier
`CyclicBarrier` 允许一组线程互相等待,直到达到某个公共屏障点(common barrier point)。与 `CountDownLatch` 不同的是,`CyclicBarrier` 在屏障点处可以执行自定义的屏障操作(barrier action),而且所有线程都必须到达屏障点才能继续执行。此外,`CyclicBarrier` 是可以重复使用的,而 `CountDownLatch` 的计数器一旦到达零,就无法重置。
#### 使用场景
- **并行计算后同步处理**:多个线程并行执行计算任务,所有线程都完成后需要进行一些共同的后续处理。
- **游戏开发**:在游戏的多人模式中,需要等待所有玩家都准备好后才能开始游戏。
#### 示例代码
考虑一个场景,一组科学家(线程)正在各自的研究领域进行研究,但他们都需要等待所有人的研究都完成后才能一起开始撰写报告。
```java
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class Scientist {
private final CyclicBarrier barrier;
public Scientist(CyclicBarrier barrier) {
this.barrier = barrier;
}
public void doResearch() {
try {
// 模拟研究过程
System.out.println(Thread.currentThread().getName() + " 开始研究...");
Thread.sleep((long) (Math.random() * 3000)); // 假设研究时间随机
System.out.println(Thread.currentThread().getName() + " 研究完成.");
// 所有科学家都研究完成后,才能继续
barrier.await();
// 所有科学家都到达屏障点后执行的操作
System.out.println(Thread.currentThread().getName() + " 开始撰写报告.");
} catch (InterruptedException | BrokenBarrierException e) {
Thread.currentThread().interrupt();
}
}
public static void main(String[] args) {
int scientists = 4; // 假设有4位科学家
CyclicBarrier barrier = new CyclicBarrier(scientists, () -> System.out.println("所有科学家都已准备好,开始撰写报告."));
for (int i = 1; i <= scientists; i++) {
new Thread(() -> new Scientist(barrier).doResearch(), "科学家" + i).start();
}
}
}
```
### 总结
`CountDownLatch` 和 `CyclicBarrier` 都是强大的并发工具,它们各自适用于不同的场景。`CountDownLatch` 适用于等待一组操作的完成,而 `CyclicBarrier` 则适用于需要所有参与者都到达某个公共点后才能继续执行的场景。在实际开发中,根据具体需求选择合适的工具,可以大大提高程序的并发性能和可读性。
通过上面的示例代码,我们可以看到,这两个工具的使用都相对直观,只需要正确设置计数器或屏障点,并在合适的位置调用 `countDown()`、`await()` 等方法即可。此外,它们还提供了异常处理机制,确保在多线程环境下程序的健壮性。
在深入理解这两个工具的基础上,你还可以探索 Java 并发包中的其他同步工具,如 `Semaphore`(信号量)、`Exchanger`(交换器)等,它们各自具有独特的功能和适用场景,能够为你的并发编程提供更多的选择。
最后,值得一提的是,码小课作为一个专注于编程学习和分享的平台,提供了丰富的课程资源和实战项目,帮助开发者不断提升自己的编程技能。如果你对并发编程或 Java 并发包中的其他内容感兴趣,不妨前往码小课网站,探索更多精彩内容。