CyclicBarrier也被称之为循环栅栏,是JUC中提供的一个同步工具类,用于解决某些需要指定现场到达某个屏障点才能继续执行后续操作的场景。
一个简单的demo示例。声明一个等待线程为5的栅栏,声明8个线程去执行,当第5个线程到达栅栏时,将会唤醒前4个等待的线程,这5个线程将执行自己的任务。CyclicBarrier将会开启下一代(新栅栏),剩余三个线程将会在新栅栏处等待,直到第5个线程到达将它们唤醒,可这里没有第5个线程,所以剩余三个线程将会一直等待。所以使用CyclicBarrier时需要对数量把控好。
public static void testCyclicBarrier() throws InterruptedException {
CyclicBarrier barrier = new CyclicBarrier(5, () -> {
System.out.println("第一次都执行完成");
});
for (int i = 0; i < 8; i++) {
final int k = i;
new Thread(() -> {
try {
Thread.sleep(3000 * (k));
//Thread.sleep(3000);
System.out.println("等待线程数" + barrier.getNumberWaiting());
barrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "执行完成");
}).start();
}
}
demo已看完,接下来探讨探讨源码是怎么实现的。CyclicBarrier的源码相比同为栅栏的CountDownLatch要简单很多。两者实现完全不一样,CountDownLatch是基于AQS共享锁实现的,CyclicBarrier是基于Condition等待唤醒实现的。CyclicBarrier内部包含以下这些属性。
// 同步锁
private final ReentrantLock lock = new ReentrantLock();
// 线程等待唤醒条件
private final Condition trip = lock.newCondition();
// 每次需要等待的线程数
private final int parties;
// 每一代完成时指定的需要执行的任务
private final Runnable barrierCommand;
// 当前代
private Generation generation = new Generation();
// 减数计数器
private int count;
CyclicBarrier主要有以下两个构造函数。只指定栅栏等待线程数parties、指定栅栏换代前执行的任务。
public CyclicBarrier(int parties) {
this(parties, null);
}
public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException();
this.parties = parties;
this.count = parties;
this.barrierCommand = barrierAction;
}
CyclicBarrier中最主要的方法await()方法,和指定等待时间的await(long timeout, TimeUnit unit)方法,两者最终都是调用的dowait(boolean timed, long nanos)方法。
dowait方法,主要作用是通过计数器count,每一个线程调用该方法都会计数(减1),看是否有指定线程数到达栅栏处,当计数器为0时意味着有指定线程到达栅栏,则唤醒等待的线程并开启下一个栅栏。否则会等待直到被唤醒。
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)
// 只有在breakBarrier()方法调用时才会破坏当前代
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();
}
}
开启下一代方法主要作用是所有等待的线程。并充值计数器,重新开启下一代。
private void nextGeneration() {
// 唤醒所有等待的线程
trip.signalAll();
// 重置计数器
count = parties;
// 重置新一代 (重置是否破坏标志)
generation = new Generation();
}
破坏栅栏方法主要将当前代标记为已破坏,并唤醒所有线程,充值计数器。
private void breakBarrier() {
// 设置当前代为已破坏
generation.broken = true;
// 重置计数器
count = parties;
// 唤醒所有线程
trip.signalAll();
}
CyclicBarrier还提供了几个方法供外部调用以获取内部一些状态。
获取正在等待的线程数量。
// 获取当前正在栅栏处等待的线程
public int getNumberWaiting() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return parties - count;
} finally {
lock.unlock();
}
}
查看当前代是否被破坏。
/**
* 返回当前代是否被破坏
* @return
*/
public boolean isBroken() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return generation.broken;
} finally {
lock.unlock();
}
}
重置栅栏。
public void reset() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 破坏当前代
breakBarrier(); // break the current generation
// 开启下一代
nextGeneration(); // start a new generation
} finally {
lock.unlock();
}
}
这类的文章网上虽然很多,但还是决定自己写一下,毕竟好记性不如烂笔头,自己写的终归有助于自己记忆。但是终究是没有别人的图文并茂来的好。