1.写在前面
CyclicBarrier 是 Java 并发编程中的一个重要工具,它允许一组线程相互等待,直到所有线程都到达某个公共屏障点。在深入阅读CyclicBarrier的代码之前,我先抛出几个问题看看大家有没有思考过:
- 什么是 CyclicBarrier?
- CyclicBarrier 和 CountDownLatch 的区别是什么?
- 如何使用 CyclicBarrier?
- CyclicBarrier 的构造方法有哪些?
- await() 方法的作用是什么?
- CyclicBarrier 的屏障可以被重用吗?
- CyclicBarrier 的 BrokenBarrierException 是什么?
- 如何处理 CyclicBarrier 中的异常?
- CyclicBarrier 的应用场景有哪些?
- CyclicBarrier 的 reset() 方法的作用是什么?
2. 从使用说起
以下是一个简单的示例,展示了如何使用 CyclicBarrier 让一组线程在某个点上相互等待。
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. Let's proceed.");
});
for (int i = 0; i < numberOfThreads; i++) {
new Thread(new Task(barrier)).start();
}
}
static class Task implements Runnable {
private CyclicBarrier barrier;
public Task(CyclicBarrier barrier) {
this.barrier = barrier;
}
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() + " is waiting at the barrier.");
barrier.await();
System.out.println(Thread.currentThread().getName() + " has crossed the barrier.");
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}
}
}
2.1 代码解释
- 设置线程数和创建 CyclicBarrier
int numberOfThreads = 3;
CyclicBarrier barrier = new CyclicBarrier(numberOfThreads, () -> {
System.out.println("All threads have reached the barrier. Let's proceed.");
});
- numberOfThreads 定义了参与的线程数量(3个)。
- 创建一个 CyclicBarrier 实例,指定线程数为 numberOfThreads,并传入一个 Runnable 作为屏障动作,当所有线程到达屏障点时执行该动作。
- 启动线程
for (int i = 0; i < numberOfThreads; i++) {
new Thread(new Task(barrier)).start();
}
使用一个循环启动指定数量的线程,每个线程执行 Task 类的实例。
- Task 类
static class Task implements Runnable {
private CyclicBarrier barrier;
public Task(CyclicBarrier barrier) {
this.barrier = barrier;
}
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() + " is waiting at the barrier.");
barrier.await();
System.out.println(Thread.currentThread().getName() + " has crossed the barrier.");
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}
}
- Task 实现了 Runnable 接口,表示一个可以在线程中执行的任务。
- Task 类中有一个 CyclicBarrier 实例,通过构造方法传入。
- run 方法中:
- 打印当前线程正在等待屏障。
- 调用 barrier.await(),使当前线程在屏障点等待,直到所有线程都到达屏障。
- 打印当前线程已经跨过屏障。
- 捕获并处理 InterruptedException 和 BrokenBarrierException 异常。
- 运行结果
当代码运行时,输出结果可能类似于以下内容(线程顺序可能不同):
Thread-0 is waiting at the barrier.
Thread-1 is waiting at the barrier.
Thread-2 is waiting at the barrier.
All threads have reached the barrier. Let's proceed.
Thread-2 has crossed the barrier.
Thread-1 has crossed the barrier.
Thread-0 has crossed the barrier.
2.2 关键点
- CyclicBarrier 的创建
- 指定需要等待的线程数量。
- 提供一个可选的 Runnable 屏障动作,当所有线程到达屏障时执行。
- await 方法
- 使线程在屏障点等待,直到所有线程都调用了 await 方法
- 当所有线程都到达屏障时,屏障动作执行,所有线程继续执行。
- 异常处理
- InterruptedException:线程在等待时被中断
- BrokenBarrierException:屏障被破坏,无法继续等待
3. CyclicBarrier 和 CountDownLatch 的区别是什么?
3.1 用途
- CyclicBarrier:允许一组线程相互等待,直到所有线程都到达某个公共屏障点。它是可重用的。
- CountDownLatch:用于让一个或多个线程等待,直到其他线程完成某些操作。它是一次性的,计数器在达到零后无法重置。
3.2 重用性
- CyclicBarrier:可以在所有线程到达屏障点后重置,允许再次使用。
- CountDownLatch:一旦计数器到达零,就不能重置,必须重新创建一个新的实例。
4. CyclicBarrier 的构造方法有哪些?
CyclicBarrier 有两个主要的构造方法
4.1 CyclicBarrier(int parties)
创建一个新的 CyclicBarrier,当指定数量的线程(parties)都调用 await() 方法时,屏障被触发。
4.2 CyclicBarrier(int parties, Runnable barrierAction)
创建一个新的 CyclicBarrier,当指定数量的线程(parties)都调用 await() 方法时,屏障被触发,并且执行指定的 barrierAction。
5. await() 方法的作用是什么?
await() 方法是 CyclicBarrier 的核心方法,调用该方法的线程会在此等待,直到所有参与的线程都调用了 await() 方法。当所有线程都到达屏障点时,屏障被触发,所有等待的线程继续执行。源码如下:
public int await() throws InterruptedException, BrokenBarrierException {
try {
return dowait(false, 0L);
} catch (TimeoutException toe) {
throw new Error(toe); // cannot happen
}
}
private int dowait(boolean timed, long nanos)
throws InterruptedException, BrokenBarrierException,
TimeoutException {
final ReentrantLock lock = this.lock;
lock.lock();
try {
final Generation g = generation;
if (g.broken)
throw new BrokenBarrierException();
if (Thread.interrupted()) {
breakBarrier();
throw new InterruptedException();
}
int index = --count;
if (index == 0) { // tripped
boolean ranAction = false;
try {
final Runnable command = barrierCommand;
if (command != null)
command.run();
ranAction = true;
nextGeneration();
return 0;
} finally {
if (!ranAction)
breakBarrier();
}
}
// loop until tripped, broken, interrupted, or timed out
for (;;) {
try {
if (!timed)
trip.await();
else if (nanos > 0L)
nanos = trip.awaitNanos(nanos);
} catch (InterruptedException ie) {
if (g == generation && ! g.broken) {
breakBarrier();
throw ie;
} else {
// We're about to finish waiting even if we had not
// been interrupted, so this interrupt is deemed to
// "belong" to subsequent execution.
Thread.currentThread().interrupt();
}
}
if (g.broken)
throw new BrokenBarrierException();
if (g != generation)
return index;
if (timed && nanos <= 0L) {
breakBarrier();
throw new TimeoutException();
}
}
} finally {
lock.unlock();
}
}
上面这段代码用于处理线程在屏障点的等待逻辑。它支持可选的超时等待,并处理各种异常情况,如线程中断和超时。
5.1 方法签名
private int dowait(boolean timed, long nanos)
throws InterruptedException, BrokenBarrierException, TimeoutException {
- timed:一个布尔值,表示是否使用超时等待。
- nanos:超时时间,单位为纳秒。
- 抛出三种异常:InterruptedException、BrokenBarrierException 和 TimeoutException。
5.2 获取锁
final ReentrantLock lock = this.lock;
lock.lock();
使用 ReentrantLock 进行线程同步,确保对共享资源的访问是线程安全的。
5.3 检查屏障状态
try {
final Generation g = generation;
if (g.broken)
throw new BrokenBarrierException();
if (Thread.interrupted()) {
breakBarrier();
throw new InterruptedException();
}
- 获取当前屏障的生成代 generation
- 如果屏障已经破坏,抛出 BrokenBarrierException
- 如果当前线程被中断,调用 breakBarrier() 方法破坏屏障,并抛出 InterruptedException
5.4 减少计数并检查是否所有线程已到达屏障
int index = --count;
if (index == 0) { // tripped
boolean ranAction = false;
try {
final Runnable command = barrierCommand;
if (command != null)
command.run();
ranAction = true;
nextGeneration();
return 0;
} finally {
if (!ranAction)
breakBarrier();
}
}
- 减少等待线程的计数 count
- 如果计数为0,表示所有线程都已到达屏障点:
- 执行屏障动作 barrierCommand(如果有)
- 调用 nextGeneration() 方法重置屏障以供下一次使用。
- 返回0,表示当前线程是最后一个到达屏障的线程。
5.5 等待屏障被触发
// loop until tripped, broken, interrupted, or timed out
for (;;) {
try {
if (!timed)
trip.await();
else if (nanos > 0L)
nanos = trip.awaitNanos(nanos);
} catch (InterruptedException ie) {
if (g == generation && !g.broken) {
breakBarrier();
throw ie;
} else {
Thread.currentThread().interrupt();
}
}
if (g.broken)
throw new BrokenBarrierException();
if (g != generation)
return index;
if (timed && nanos <= 0L) {
breakBarrier();
throw new TimeoutException();
}
}
- 进入一个无限循环,直到屏障被触发、破坏、中断或超时。
- 如果 timed 为 false,调用 trip.await() 使线程等待。
- 如果 timed 为 true 且 nanos 大于0,调用 trip.awaitNanos(nanos) 使线程等待指定的纳秒时间。
- 捕获 InterruptedException 异常
- 如果当前屏障未破坏且仍在当前代,调用 breakBarrier() 破坏屏障,并重新抛出异常。
- 否则,将当前线程标记为中断状态。
- 如果屏障被破坏,抛出 BrokenBarrierException。
- 如果屏障代已更新,返回当前线程的索引。
- 如果超时且 nanos 小于等于0,调用 breakBarrier() 破坏屏障,并抛出 TimeoutException。
5.6 释放锁
} finally {
lock.unlock();
}
在 try 块的 finally 部分释放锁,确保锁总是被释放以避免死锁。
6.CyclicBarrier 的 BrokenBarrierException 是什么?
BrokenBarrierException 是一个异常,当等待在 CyclicBarrier 的线程被中断或屏障被重置时,会抛出这个异常。它表示屏障已经被破坏,无法继续等待。
7. 如何处理 CyclicBarrier 中的异常?
在使用 CyclicBarrier 时,需要处理 InterruptedException 和 BrokenBarrierException。通常在 await() 方法的调用处进行异常处理。
try {
barrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
8. CyclicBarrier 的应用场景有哪些?
- 并行计算:将一个大任务拆分成多个子任务,并行执行,所有子任务完成后再汇总结果。
- 模拟多线程并发:在测试中,模拟多个线程在同一时刻开始执行某些操作。
- 多阶段任务:将任务分为多个阶段,每个阶段的所有线程完成后再进入下一阶段。
9. CyclicBarrier 的 reset() 方法的作用是什么?
reset() 方法用于将 CyclicBarrier 重置为初始状态。如果有任何线程正在等待屏障,它们会抛出 BrokenBarrierException。这个方法可以在需要重新使用屏障时调用。
系列文章
7.jdk源码阅读之ConcurrentHashMap(上)