CyclicBarrier
Also called loop fence, can be achieved to allow a set of threads running at the fence and blocking function until all threads have reached the fence and then perform together. "Loop" means that CyclicBarrier
can be used repeatedly, compared to CountDownLatch
only be used once, CyclicBarrier
you can save a lot of resources, and can also pass task in the constructor, to perform this task when the fence conditions are met. CyclicBarrier
Is used ReentrantLock
, the primary method will be locked at the time of execution, therefore concurrent performance is not very high.
1. related fields
//重入锁,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;
Note that the generation
field used to mark the current fence in the generation. When certain conditions are satisfied (e.g., called reset
methods, etc. or a fence is opened), the state will switch to the next fence, it is actually new
a new Generation
object, which is CyclicBarrier
an internal class, code that is very simple, as follows:
private static class Generation {
boolean broken = false; //标记栅栏是否被破坏
}
In actual use, we will use generation
the fields determine whether the current in the same generational, using broker
field determines whether the barrier is destroyed.
2. Constructor
CyclicBarrier
There are two overloaded constructor, constructor simply related to the above fields are initialized as follows:
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;
}
3. The core method
await
await
Is the most commonly used method of developing the sameCountDownLatch
, likeCyclicBarrier
also provides twoawait
methods, one with no parameters, with a timeout parameter, simply call the inside a littledowait
method:
public int await() throws InterruptedException, BrokenBarrierException {
try {
return dowait(false, 0L);
} catch (TimeoutException toe) {
throw new Error(toe); // cannot happen
}
}
public int await(long timeout, TimeUnit unit)
throws InterruptedException,
BrokenBarrierException,
TimeoutException {
return dowait(true, unit.toNanos(timeout));
}
Then take a look critical dowait
method:
private int dowait(boolean timed, long nanos)
throws InterruptedException, BrokenBarrierException,
TimeoutException {
final ReentrantLock lock = this.lock;
//加重入锁
lock.lock();
try {
//首先获取年龄代信息
final Generation g = generation;
//如果栅栏状态被破坏,抛出异常,例如先启动的线程调用了breakBarrier方法,后启动的线程就能够看到g.broker=true
if (g.broken)
throw new BrokenBarrierException();
//检测线程的中断状态,如果线程设置了中断状态,则通过breakBarrier设置栅栏为已破坏状态,并唤醒其他线程
//如果这里能够检测到中断状态,那只可能是在await方法外部设置的
if (Thread.interrupted()) {
breakBarrier();
throw new InterruptedException();
}
//每调用一次await,就将需要等待的线程数减1
int index = --count;
//index=0表示这是最后一个到达的线程,由该线程执行下面的逻辑
if (index == 0) { // tripped
boolean ranAction = false;
try {
final Runnable command = barrierCommand;
//如果在构造器中传入了第二个任务参数,就在放开栅栏前先执行这个任务
if (command != null)
command.run();
ranAction = true;
//正常结束,需要唤醒阻塞的线程,并换代
nextGeneration();
return 0;
} finally {
//try代码块如果正常执行,ranAction就一定等于true,而try代码块唯一可能发生异常的地方就是command.run(),
//因此这里为了保证在任务执行失败时,将栅栏标记为已破坏,唤醒阻塞线程
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) {
//如果线程等待的过程中被中断,会执行到这里
//g == generation表示当前还在同一个年龄分代中,!g.broker表示当前栅栏状态没有被破坏
if (g == generation && ! g.broken) {
breakBarrier();
throw ie;
} else {
//上面的条件不满足,说明:1)g!=generation,说明线程执行到这里时已经换代了
//2)没有换代,但是栅栏被破坏了
//无论哪种情况,都只是简单地设置一下当前线程的中断状态
Thread.currentThread().interrupt();
}
}
//栅栏被破坏,抛出异常
//注意,在breakBarrier方法中会唤醒所有等待条件的线程,这些线程会执行到这里,判断栅栏已经被破坏,都会抛出异常
if (g.broken)
throw new BrokenBarrierException();
//距离上一次设置g变量的值已经过去很长时间了,在执行过程中generation可能已经发生改变,
//当前线程还是前几代的,不需要再循环阻塞了,直接返回上一代剩余需要等待的线程数
//注意:代码中breakBarrier方法和nextGeneration方法都会唤醒阻塞的线程,但是breakBarrier在上一个判断就被拦截了,
//因此走到这里的有三种情况:
//a)最后一个线程正常执行,栅栏打开导致其他线程被唤醒;不属于当前代的线程直接返回,
//属于当前代的则可能因为没到栅栏开放条件要继续循环阻塞
//b)栅栏被重置(调用了reset方法),此时g!=negeration,全都直接返回
//c)线程等待超时了,不属于当前代的返回就可以了,属于当前代的则要设置generation.broken = true
if (g != generation)
return index;
//如果线程等待超时,标记栅栏为破坏状态并抛出异常,如果还没超时,则自旋后又重新阻塞
if (timed && nanos <= 0L) {
breakBarrier();
throw new TimeoutException();
}
}
} finally {
//别忘了解锁
lock.unlock();
}
}
dowait
The method logic is: each call await
to the thread count will be count
reduced 1
, the last thread will be count
reduced to 0
time, incidentally, also perform barrierCommand
specified tasks and generation
switch to the next generation, of course, the most important thing is to wake up before the fence at blocked thread. Due to trip
the corresponding Condition
object has no place will be modified so that trip.signalAll()
will awaken all waiting on the condition of the thread, if the thread is waiting for the process, the other thread will generation
be updated to the next generation, there will be awakened thread some also belong prior to that generation situation.
The next will be on dowait
some of the methods used for a brief introduction.
breakBarrier
dowait
There are four local method callbreakBarrier
, it can see from the name, which will begeneration.broken
settrue
, in addition, will restorecount
the values and wakes up all the blocked thread:
private void breakBarrier() {
generation.broken = true;
count = parties;
//唤醒所有的阻塞线程
trip.signalAll();
}
Looking at the CyclicBarrier
source code, generation.broken
unified breakBarrier
is set to approach true
, but once generation.broken
set true
later, after checking the code will throw an exception to this state, there is no way to re-use the fence (you can manually call reset
reset), and the source code in the following situations will call the breakBarrier
method:
1) the current thread is interrupted
2) passed through the constructor task fails
interrupted 3) condition waiting
4) thread waits timeout
5) explicitly call the reset
method
nextGeneration
private void nextGeneration() {
// 唤醒所有的阻塞线程
trip.signalAll();
// 开启下一代
count = parties;
generation = new Generation();
}
reset
reset
The main method is the end of this generation, and switch to the next generation
public void reset() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
breakBarrier(); // break the current generation
nextGeneration(); // start a new generation
} finally {
lock.unlock();
}
}
Introduced here, the whole CyclicBarrier
has been introduced almost over, but the internal process is far from simple, because the logic is encapsulated in a large part AbstractQueuedSynchronizer
, this class defines how threads are blocked waiting queue, but also how to be woken up, so if you want depth understanding of logical thread to wait, but also need to carefully study AbstractQueuedSynchronizer
the job. This article describes this part of the content will not be behind if you have time will be a special presentation.