CyclicBarrier同步器经常被问到,跟CountDownLatch有什么区别。区别还是很大的。
我们从源码看一下:
继承关系及属性
public class CyclicBarrier {
private static class Generation {
boolean broken = false;
}
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();
private int count;
}
内部有一个可重入锁lock,一个lock的关联条件trip。锁的数量parties。剩余锁计数器count;
构造方法
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);
}
可以看到,可以传入锁的数量,同步计数器为paties和一个到达该数量之后的执行方法。
主要方法
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();
}
//计数器减1
int index = --count;
//如果计数器为0,说明该轮已经到达要求数量
if (index == 0) { // tripped
boolean ranAction = false;
try {
final Runnable command = barrierCommand;
//如果有此参数,执行该方法。注意,直接调用run(),同步阻塞
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();
}
}
总结
CyclicBarrier并未直接使用AQS,而是通过ReentratLock和condition实现的。整个流程就很清楚,先传入一个计数器数量,进来一个线程就消耗一个,如果数量不为零,就使用condition.await挂起本线程。如果数量消耗到零,就执行预定方法,并进入下一轮,及重置计数器,唤醒所有之前挂起的方法。
跟CountDownLatch的区别
CountDownLatch是内部类Sync直接继承自AQS,并重写了tryAcquireShare和tryReleaseShare方法。实现了当state不为零时将线程入队挂起,在tryReleaseShare执行减一后,如果state为0则,逐个将入队的线程唤醒的操作。是一次性的操作。
使用示例
public class CyclicBarrierTest {
static class MyThread extends Thread{
CyclicBarrier cyclicBarrier;
public MyThread(CyclicBarrier cyclicBarrier) {
this.cyclicBarrier=cyclicBarrier;
}
@Override
public void run() {
try {
Thread.sleep(5000);
System.out.println("线程 "+Thread.currentThread().getName()+" 就位,等待其他线程");
cyclicBarrier.await();
System.out.println("线程 "+Thread.currentThread().getName()+" 开始执行自己的业务");
Thread.sleep(2000);
cyclicBarrier.await();
System.out.println("线程 "+Thread.currentThread().getName()+" 自己的业务执行完成");
}catch (InterruptedException e){
e.printStackTrace();
}catch (BrokenBarrierException e){
e.printStackTrace();
}
}
}
public static void main(String[] args) {
int count=5;
CyclicBarrier cyclicBarrier=new CyclicBarrier(count,()->{
System.out.println("5 threads done");
});
for (int i=0;i<count;i++){
new MyThread(cyclicBarrier).start();
}
System.out.println("main thread run");
}
}
运行结果
main thread run
线程 Thread-1 就位,等待其他线程
线程 Thread-0 就位,等待其他线程
线程 Thread-3 就位,等待其他线程
线程 Thread-2 就位,等待其他线程
线程 Thread-4 就位,等待其他线程
5 threads done
线程 Thread-4 开始执行自己的业务
线程 Thread-1 开始执行自己的业务
线程 Thread-3 开始执行自己的业务
线程 Thread-0 开始执行自己的业务
线程 Thread-2 开始执行自己的业务
5 threads done
线程 Thread-0 自己的业务执行完成
线程 Thread-2 自己的业务执行完成
线程 Thread-1 自己的业务执行完成
线程 Thread-3 自己的业务执行完成
线程 Thread-4 自己的业务执行完成
可以看到五个线程相互等待,到齐后执行预定栅栏操作,然后再进行下一轮的等待。