CyclicBarrier翻译过来是回环栅栏,它可以实现让一组线程等待至某个状态之后再全部同步执行,这个状态叫屏障点。当所有等待线程都被释放后,CyclicBarrier可以重置再次使用。
CyclicBarrier的功能是由ReentrantLock和Condition共同实现的,因此在其内部拥有ReentrantLock类型的lock属性和Condition类型的trip属性。此外,还有用于保存该屏障拦截的线程数parties属性和当前剩余等待的线程数count属性。这些属性的作用在后面我们详细介绍源码时再详细介绍。
CyclicBarrier的构造函数如下:
//parties表示当前屏障拦截的线程数
public CyclicBarrier(int parties) {
this(parties, null);
}
//barriesAction表示当所有线程都到达屏障时首先执行的行为
public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException();
this.parties = parties;
this.count = parties;
this.barrierCommand = barrierAction;
}
常用方法如下:
由于CyclicBarrier的计数器可以重置,屏障可以重复使用,因此当paries的整数倍数量的线程调用await()方法时程序都是可以正常结束的,否则由于还有线程在阻塞,程序会一直阻塞不会结束。例如下面的程序中定义了屏障的拦截线程数paties=3,随后分别在7个线程中调用CyclicBarrier的await()方法。
public class CyclicBarrierTest {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
for (int i = 0; i < 7; i++) {
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName()
+ "开始等待其他线程");
cyclicBarrier.await();
System.out.println(Thread.currentThread().getName() + "开始执行");
Thread.sleep(5000);
System.out.println(Thread.currentThread().getName() + "执行完毕");
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
}
控制人打印结果如下图,可以发现程序没有结束,因此线程Thread-6还在阻塞。
由于CyclicBarrier是借助ReentrantLock和Condition实现的,因此我们在深入解析CyclicBarrier源码之前先来简单介绍下ReentrantLock和Condition是怎么工作的。
我们都知道AQS是基于MESA管程模型实现的,ReentrantLock是AQS的独占锁实现,而Condition可以理解为是MESA模型中的条件变量,那么当执行下面的程序时锁(ReentrantLock)是怎么变化的呢?需要注意的是,下面的程序中只有一个ReentrantLock对象,因此只有一把锁。
public class ReentrantLockDemo6 {
private static ReentrantLock lock = new ReentrantLock();
private static Condition cigCon = lock.newCondition();
private static Condition takeCon = lock.newCondition();
private static boolean hashcig = false;
private static boolean hastakeout = false;
//送烟
public void cigratee(){
lock.lock();
try {
while(!hashcig){
try {
log.debug("没有烟,歇一会");
cigCon.await();
}catch (Exception e){
e.printStackTrace();
}
}
log.debug("有烟了,干活");
}finally {
lock.unlock();
}
}
//送外卖
public void takeout(){
lock.lock();
try {
while(!hastakeout){
try {
log.debug("没有饭,歇一会");
takeCon.await();
}catch (Exception e){
e.printStackTrace();
}
}
log.debug("有饭了,干活");
}finally {
lock.unlock();
}
}
public static void main(String[] args) {
ReentrantLockDemo6 test = new ReentrantLockDemo6();
new Thread(() ->{
test.cigratee();
},"t1").start();
new Thread(() -> {
test.takeout();
},"t2").start();
new Thread(() ->{
lock.lock();
try {
hashcig = true;
log.debug("唤醒送烟的等待线程");
cigCon.signal();
}finally {
lock.unlock();
}
},"t3").start();
new Thread(() ->{
lock.lock();
try {
hastakeout = true;
log.debug("唤醒送饭的等待线程");
takeCon.signal();
}finally {
lock.unlock();
}
},"t4").start();
}
}
控制台打印结果如下:
关于以上,需要做更多的说明:
我们可以通过跟踪CyclicBarrier的源码来达到学习CyclicBarrier以及ReentrantLock与Condition协作的目的。
这里是从CyclicBarrier的await()方法一步一步地介绍的,较复杂,如果不想看可以直接跳到这一部分后面总结的await()方法执行的流程图部分。
我们使用下面的程序来切入CyclicBarrier源码的介绍。在下面的程序中,使用三个线程分别计算三位同学的平均分,三个线程都计算完成后再计算三位同学的平均分。
public class CyclicBarrierTest2 {
//保存每个学生的平均成绩
private ConcurrentHashMap map=new ConcurrentHashMap();
private ExecutorService threadPool= Executors.newFixedThreadPool(3);
private CyclicBarrier cb=new CyclicBarrier(3,()->{
int result=0;
Set set = map.keySet();
for(String s:set){
result+=map.get(s);
}
System.out.println("三人平均成绩为:"+(result/3)+"分");
});
public void count(){
for(int i=0;i<3;i++){
threadPool.execute(new Runnable(){
@Override
public void run() {
//获取学生平均成绩
int score=(int)(Math.random()*40+60);
map.put(Thread.currentThread().getName(), score);
System.out.println(Thread.currentThread().getName()
+"同学的平均成绩为:"+score);
try {
//执行完运行await(),等待所有学生平均成绩都计算完毕
cb.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}
});
}
}
public static void main(String[] args) {
CyclicBarrierTest2 cb=new CyclicBarrierTest2();
cb.count();
}
}
当线程t1首先计算完成执行cb.await()方法,await()方法实现如下:
public int await() throws InterruptedException, BrokenBarrierException {
try {
return dowait(false, 0L);
} catch (TimeoutException toe) {
throw new Error(toe); // cannot happen
}
}
dowait()方法实现如下:
private int dowait(boolean timed, long nanos)
throws InterruptedException, BrokenBarrierException,
TimeoutException {
//1.使用CyclicBarrier的lock对象加锁
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();
}
//2.当前线程到达屏障,因此count数量-1,判断计算后的值是否为0
int index = --count;
if (index == 0) {
boolean ranAction = false;
try {
final Runnable command = barrierCommand;
if (command != null)
command.run();
ranAction = true;
nextGeneration();
return 0;
} finally {
if (!ranAction)
breakBarrier();
}
}
//3.当前线程阻塞,直到被唤醒、中断或超时
for (;;) {
try {
if (!timed)
//4.调用CyclicBarrier的Condition的await()方法
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();
}
}
} finally {
lock.unlock();
}
}
这里需要特别说明的是,Condition是一个接口,其实现类是AQS中的一个内部类ConditionObject,在其内部维护了条件等待队列的首节点Node对象firstWaiter和尾结点lastWaiter,另外还包括一些维护条件队列出队入队操作的方法。此处调用的trip的await()方法当然是Condition接口的await()方法,实现如下:
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
//1
Node node = addConditionWaiter();
//2
int savedState = fullyRelease(node);
int interruptMode = 0;
//3
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
//4
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
首先进入addConditionWaiter()方法,实现如下:
private Node addConditionWaiter() {
Node t = lastWaiter;
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
然后回到await()方法,进入2处的fullRelease()方法,其实现逻辑如下:
final int fullyRelease(Node node) {
boolean failed = true;
try {
int savedState = getState();
if (release(savedState)) {
failed = false;
return savedState;
} else {
throw new IllegalMonitorStateException();
}
} finally {
if (failed)
node.waitStatus = Node.CANCELLED;
}
}
需要注意的是,此时还在CyclicBarrier的lock锁内,此处调用release()方法释放锁(主要就是将state设为0,ReentrantLock的表示独占锁拥有者的exclusiveOwnerThread属性置为null),关于release()方法我们在介绍ReentrantLock的文章中介绍过,此处不再赘述。
再返回await()方法的代码3处,isOnSyncQueue()方法的实现如下:
final boolean isOnSyncQueue(Node node) {
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
if (node.next != null)
return true;
return findNodeFromTail(node);
}
由于线程t1节点是在条件等待队列中,其状态是CONDITION,因此此处直接返回false。
再返回await()方法,可以看到isOnSyncQueue()方法返回false后就执行了LockSupport的park()方法将当前线程t1阻塞。
此时线程t1内部执行CyclicBarrier的await()方法的逻辑就暂时完成了,总结如下:
随后线程t2也执行到了await()方法,此处我们不再一步步地跟踪了,其执行逻辑如下:
最后线程t3也执行到了await()方法,前两个步骤与线程t1和t2一样:
在第三个步骤dowait()方法,判断count的值为0,此处与线程t3与前面的线程逻辑不再一样了,相关部分代码如下:
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()方法,实现如下:
private void nextGeneration() {
trip.signalAll();
count = parties;
generation = new Generation();
}
可以看到又调用了Condition的signalAll()方法,实现如下:
public final void signalAll() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignalAll(first);
}
首先判断当前线程t2是否是获取独占锁的线程,是的。随后判断条件队列的首节点即线程t1节点不为空,调用doSignalAll()方法,实现如下:
private void doSignalAll(Node first) {
lastWaiter = firstWaiter = null;
do {
Node next = first.nextWaiter;
first.nextWaiter = null;
transferForSignal(first);
first = next;
} while (first != null);
}
在我们这个例子中循环了两次,分别为线程t1和线程t2的节点执行transferForsignal()方法,transferForsignal()方法实现如下:
final boolean transferForSignal(Node node) {
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
Node p = enq(node);
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
线程t2的逻辑到此处就执行完成了,一层层的方法返回,最终CyclicBarrier的doWait()方法的finally块中执行lock.unpark()方法。unpark()方法唤醒的是当前lock对象的同步等待队列的头节点,在当前lock锁竞争激烈的场景下,头节点有可能不是线程t1或者线程t2。此处需要注意的是,当线程t1或线程t2获得CPU继续执行时,应该在它们被阻塞的地方继续执行,即在AQS的ConditionObject的await()方法的代码4处(翻到前面的代码块)继续执行。继续执行的acquireQueued()方法是循环调用CAS获取lock锁的逻辑,因此线程t1和t2在此处会再次获取到锁,执行我们自己的代码中CyclicBarrier的await()方法之后的逻辑。