1 概述
和CountDownLatch类相似,CyclicBarrier类是Java的j.u.c包中的一个线程同步的辅助类,其主要功能是让一组线程互相等待,直到抵达某个公共屏障点,这个障碍点(即barrier)也可以称为同步点。由于这个barrier在释放等待线程后可以被重用,因此被称为循环的,即cyclic。
2 典型应用
CyclicBarrier类的典型应用场景是这样的:当某些任务需要协同工作,只有当各个成员都完成了某一阶段的任务时,才能接着进行下一步的工作。
一个典型的例子就是旅游团组织大家去某个地方旅游,导游在开始旅游的时候都会约定某个景点游玩之后大家在某个地方集合,当所有的人都到齐了以后才接着游玩下一个景点。将这个场景抽象成代码如下
import java.util.Random;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Created by fubinhe on 16/11/6.
*/
public class CyclicBarrierDemo {
public static void main(String[] args) {
CyclicBarrier cb = new CyclicBarrier(3);
ExecutorService exec = Executors.newFixedThreadPool(3);
for (int i = 1; i <= 3; ++i) {
exec.execute(new Tourist("tourist " + i, cb));
}
exec.shutdown();
}
}
class Tourist implements Runnable {
private String name;
private CyclicBarrier cb;
public Tourist(String name, CyclicBarrier cb) {
this.name = name;
this.cb = cb;
}
@Override
public void run() {
// 这里的3表示有3个景点需要游玩
for (int i = 1; i <= 3; ++i) {
try {
Thread.sleep(new Random().nextInt(2000));
int waitingNum = cb.getNumberWaiting() + 1;
System.out.println(this.name + " is arriving at spot " + i + ", " +
waitingNum + " tourists are waiting.");
// 这里的3则表示总共有3个游客
if (waitingNum == 3) {
System.out.println("all arrived, go on......");
}
cb.await();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
在上述代码中,假定有3个游客需要游玩3个地方。当某个游客到达一个景点以后调用CyclicBarrier对象的await方法进行等待,只有3个游客都调用了该方法才能继续往下执行。同时,可以通过调用CyclicBarrier对象的getNumberWaiting方法可以知道当前有多少个游客在等待。具体的执行结果如下图所示
3 源码浅析
3.1 成员属性
/** The lock for guarding barrier entry */
private final ReentrantLock lock = new ReentrantLock();
/** Condition to wait on until tripped */
private final Condition trip = lock.newCondition();
/** The number of parties */
private final int parties;
/* The command to run when tripped */
private final Runnable barrierCommand;
/** The current generation */
private Generation generation = new Generation();
各个成员属性的含义在注释之中已有体现,其中,parties表示必须有多少个线程需要到达barrier,barrierCommand表示当所有线程都到达后需要执行的代码,count表示还有几个个线程未到达barrier,generation表示当前执行的“代数”,这个“代数”是一次循环的标志,体现了CyclicBarrier类的课循环性。这里的Generation是一个私有内部类,只有一个成员属性broken表示当前的barrier是否遭到“损坏”。
3.2 构造函数
CyclicBarrier有两个构造函数,主要是传入parties参数,告知有几个线程需要等待
public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException();
this.parties = parties;
this.count = parties;
this.barrierCommand = barrierAction;
}
public CyclicBarrier(int parties) {
this(parties, null);
}
3.3 await函数
CyclicBarrier的同步功能主要是通过可重入独占锁和Condition对象来实现的,某个线程到达barrier的标志是执行了await函数进行等待,该函数的源代码如下
public int await() throws InterruptedException, BrokenBarrierException {
try {
return dowait(false, 0L);
} catch (TimeoutException toe) {
throw new Error(toe); // cannot happen;
}
}
可以看到其实际是执行了dowait函数,从下面的源代码中可以看到,该函数还提供等待一定时间的功能。
函数的开头执行锁同步,主要是为了同步各个成员变量的并发访问。然后做了两个判断,分别处理该线程在等待锁的过程中barrier被破坏和该线程在等待锁的过程中被中断的情况,处理的方式是分别抛出相应的异常。
接下来将count变量减1,如果此时count为0,表示所有的线程到达barrier,然后执行barrierCommand中的代码(如果barrierCommand对象为空就不需要执行)并且开启下一代的barrier;如果count尚未到达0,则在Condition对象trip上执行await操作,而该对象的唤醒是在count为0时的finally代码中的breakBarrier函数执行的。而在Condition对象trip执行await函数的代码块中有一个for循环并处理了一些InterruptedException,具体的流程可以参考注释。
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();
}
}
3.4 getNumberWaiting函数
getNumberWaiting函数是获取当前有多少个线程在等待,其源代码如下,原理非常简单,就是并发的访问成员属性parties和count。
public int getNumberWaiting() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return parties - count;
} finally {
lock.unlock();
}
}
4 总结
CyclicBarrier是控制一些线程相互等待,直到所有的线程都到达某个barrier以后才能继续执行。它和CountDownLatch类的作用类似,但是有着微妙的差别,网上很多人说它们的区别在于CyclicBarrier是循环的,而CountDownLatch是一次性的。当然,这也是它们的一个不同点,但是更主要的不同是,CountDownLatch是让一个(或某些)线程等待另外一个(或某些)线程,而CyclicBarrier是控制某些线程之间相互等待的。