CyclicBarrier source code analysis how to kill CountDownLatch
What are Cyclic Barriers?
CyclicBarrier
Commonly known as 栅栏
barriers, it is a multi-threaded synchronization tool.
It is often compared CountDownLatch
with , because both are tools for waiting for a group of threads.
Different from the coordinating thread CountDownLatch
that is often used to wait for a group of worker threads, and the worker threads will be notified once and continue to execute after they arrive CyclicBarrier
. As the name suggests, a group of threads wait for each other to arrive at the designated place before continuing to execute.
CyclicBarrier
More advanced would be:
-
Reuse is allowed, and can be reused after execution or manual reset;
CountDownLatch
directly scrapped. -
Callback logic is allowed to be executed, usually the last thread to reach the fence calls itself. We can use this feature to perform finishing touches in our business logic.
-
If there is a problem, the current generation can be destroyed (notify other threads), and reset for the next time. It is different from not caring about the result after
CountDownLatch
reaching the specified condition,CyclicBarrier
and must wait on the fence. If a worker thread gets an exception before it arrives, it will need to be manually reset.Of course, abnormalities, timeouts, etc. will automatically destroy the current use, but note that it cannot be used directly for the next time, and must be reset manually.
How to use Cyclic Barrier?
CountDownLatch: A safe with several locks (juejin.cn) cue CyclicBarrier
says that after opening the safety lock, it can no longer be used.
Allow reuse
Of course, the cronies hope that after the boss unlocks and takes the money and runs away, the boss will not find out when he comes back to check.
private static void normal() throws InterruptedException {
CyclicBarrier barrier = new CyclicBarrier(3);
if (!barrier.isBroken()) {
System.out.println("保险箱:安全保护中");
}
System.out.println("亲信们:不成功便成仁!!!!!");
for (int i = 0; i < 3; i++) {
Thread thread = new Thread(new MyFollower(barrier));
thread.start();
}
Thread.sleep(1000);
if (!barrier.isBroken()) {
System.out.println("保险箱:安全保护中");
System.out.println("老板:很好,这东西不错");
}
}
private static class MyFollower implements Runnable {
private CyclicBarrier barrier;
public MyFollower(CyclicBarrier barrier) {
this.barrier = barrier;
}
@Override
public void run() {
System.out.println("亲信:输入密码ing");
try {
barrier.await();
} catch (InterruptedException e) {
System.out.println("临死前:骂骂咧咧地退出了游戏");
} catch (BrokenBarrierException e) {
System.out.println("OS:傻子,这都输入错了");
}
System.out.println("亲信:成了!!!!!");
}
}
保险箱:安全保护中
亲信们:不成功便成仁!!!!!
亲信:输入密码ing
亲信:输入密码ing
亲信:输入密码ing
亲信:成了!!!!!
亲信:成了!!!!!
亲信:成了!!!!!
保险箱:安全保护中
老板:很好,这东西不错
In the case of a trusted friend being killed (interruption), wrong input (voluntary destruction), or slow input (timeout), what does it matter to my safety lock?
Since internal thread interruption, or active destruction, checks a boolean value, other threads cannot know what caused the destruction in this case.
Simple code implementation (the real situation definitely needs more perfect coordination and processing)
private static void normal() throws InterruptedException {
CyclicBarrier barrier = new CyclicBarrier(3);
if (!barrier.isBroken()) {
System.out.println("保险箱:安全保护中");
}
System.out.println("亲信们:不成功便成仁!!!!!");
for (int i = 0; i < 3; i++) {
Thread thread = new Thread(new MyFollower(barrier, i + 1));
thread.start();
if (i == 0) {
thread.interrupt();
}
}
Thread.sleep(2000);
if (barrier.isBroken()) {
System.out.println("亲信:重置一下");
barrier.reset();
}
if (!barrier.isBroken()) {
System.out.println("保险箱:安全保护中");
System.out.println("老板:很好,这东西不错");
}
}
private static class MyFollower implements Runnable {
private CyclicBarrier barrier;
private int no;
public MyFollower(CyclicBarrier barrier, int no) {
this.barrier = barrier;
this.no = no;
}
@Override
public void run() {
System.out.println("亲信:输入密码ing");
if (no == 3) {
System.out.println("亲信:输错了...重置一下");
barrier.reset();
return;
}
if (no == 2) {
System.out.println("亲信:输慢了...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return;
}
try {
barrier.await(1, TimeUnit.SECONDS);
} catch (InterruptedException e) {
System.out.println("临死前:骂骂咧咧地退出了游戏");
return;
} catch (BrokenBarrierException e) {
System.out.println("OS:哪个傻子错了啊");
return;
} catch (TimeoutException e) {
System.out.println("亲信:是谁输入慢了");
return;
}
System.out.println("亲信:成了!!!!!");
}
Manufacturer's custom unlock result (callback function)
Manufacturers of safety locks allow consumers to customize celebrations, notifications, etc. after unlocking. How to do it?
private static void normal() throws InterruptedException {
CyclicBarrier barrier = new CyclicBarrier(3, () -> {
System.out.println("保险箱:恭喜主人,又拿到钱了!!!");
});
if (!barrier.isBroken()) {
System.out.println("保险箱:安全保护中");
}
System.out.println("亲信们:不成功便成仁!!!!!");
for (int i = 0; i < 3; i++) {
Thread thread = new Thread(new MyFollower(barrier));
thread.start();
}
Thread.sleep(1000);
if (!barrier.isBroken()) {
System.out.println("保险箱:安全保护中");
System.out.println("老板:很好,这东西不错");
}
}
保险箱:安全保护中
亲信们:不成功便成仁!!!!!
亲信:输入密码ing
亲信:输入密码ing
亲信:输入密码ing
亲信:成了!!!!!
亲信:成了!!!!!
亲信:成了!!!!!
保险箱:恭喜主人,又拿到钱了!!!
保险箱:安全保护中
老板:很好,这东西不错
Note: The callback function can do anything, depending on how it is used. But this is executed by the last worker thread to reach the fence.
CyclicBarrier source code
CyclicBarrier
Let's consider it under the AQS system, but it does not directly inherit and extend AQS CountDownLatch
internally , but uses the same internally extended AQS ReentrantLock
.
About AQS and
ReenrtantLock
can view:
Based on this, we speculate about the internal structure CyclicBarrier
of :
- There must be a lock, in order to ensure the concurrency of multiple threads
- Conditional, for collective waiting at the fence
- There must be a counter to calculate whether all have arrived
- To have the number of participants, used to reset the counter
- Must have a broken state
- There must be a generational measure to ensure that the reset counter and state will not affect the previous one.
- To save the callback function.
properties and inner classes
private static class Generation {
Generation() {
} // prevent access constructor creation
// 标识栅栏破坏,线程不会再等待
// 一般几种情况:
// 1.等待超时
// 2.线程中断
// 3.手动重置
// 4.回调执行异常
boolean broken; // initially false
}
// 用于控制对状态、数量操作的并发控制
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();
// 剩下还没达到等待栅栏的线程数量, 每次到达就 --
// 初始值为 parties;
// parties - count = 正在等待的线程数量
private int count;
Comparative speculation:
- There must be a lock
ReentrantLock lock
, in order to ensure the concurrency of multiple threads - Conditional
Condition trip
, for collective waiting at the fence - There must be a counter
count
to calculate whether all have arrived - To have the number of participants
parties
, used to reset the counter - Must have a broken state
generation.broken
- There must be a generational measure
generation
to ensure that the reset counter does not affect the previous one. - To save the callback function
barrierCommand
.
However Generation
, the generational processing of is relatively simple, and will not retain other states of the previous generation, and will be reset directly; only the damaged state is retained, which is used to limit the exit of threads that are still in the execution process of the previous generation.
So the reset and notify methods are as follows:
Note: To avoid confusion, generally internal calls to these methods must only require locks
into the next generationnextGeneration
Being able to successfully enter the next generation means that all have successfully reached the barrier, and the callback function has been executed successfully.
// 进入下一个分代
private void nextGeneration() {
// signal completion of last generation
// 唤醒上一个分代中的等待线程
trip.signalAll();
// 重置 count 计数
count = parties;
generation = new Generation();
}
flag vandalism
The general situation is interruption, timeout, execution failure, etc.
private void breakBarrier() {
generation.broken = true;
count = parties;
// 唤醒当前分代中的等待线程
trip.signalAll();
}
It can be seen that the two operations of notifying the thread must be accompanied by the reset count
of , the difference is that generation
the and are operated respectively twice broken
.
Explain that when a thread waits for the fence, it will judge two values according to different situations.
Constructor
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);
}
core method
await
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));
}
It can be seen that the waiting method is called internally dowait
. Judging from the exception list of the method, it is a large and comprehensive processing method that can run out of interruption, destruction, and timeout exceptions.
dowait
/**
* @param timed 是否允许超时
* @param nanos 超时时间, 只有 timed = true 才有意义
* @return 线程到达栅栏的索引: 第一个:parties - 1; 最后一个:0
*/
private int dowait(boolean timed, long nanos)
throws InterruptedException, BrokenBarrierException,
TimeoutException {
final ReentrantLock lock = this.lock;
// 1.核心逻辑得加锁
lock.lock();
try {
final Generation g = generation;
if (g.broken)
// 2.如果已经破坏, 必须手动重置才能使用
throw new BrokenBarrierException();
if (Thread.interrupted()) {
// 3.提前检查当前执行线程已经中断了, 那收尾:破坏栅栏,顺便唤醒所有等待线程
breakBarrier();
throw new InterruptedException();
}
int index = --count;
if (index == 0) {
// 4.全部到达了,当前线程是最后一个
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
// 5.循环等待,直到中断、破坏、超时的任意情况发生
for (;;) {
try {
// 判断是超时或不超时等待
if (!timed)
trip.await();
else if (nanos > 0L)
nanos = trip.awaitNanos(nanos);
} catch (InterruptedException ie) {
// 6.判断是外部还是内部中断
if (g == generation && ! g.broken) {
// 6.1说明是外部其他线程中断的
// 进行收尾的同时, 得抛出异常
breakBarrier();
throw ie;
} else {
// 6.2如果进行到这里,那肯定是属于栅栏被破坏,或者全部到达栅栏
// 直接恢复中断状态,不需要处理异常
Thread.currentThread().interrupt();
}
}
if (g.broken)
// 7。检查是不是属于被破坏唤醒
throw new BrokenBarrierException();
if (g != generation)
// 8。是否结束了, 相当于返回序号
return index;
// 9.没结束,判断是否超时:超时收尾
if (timed && nanos <= 0L) {
breakBarrier();
throw new TimeoutException();
}
// 我也不清除啥情况会继续循环.
// 如果会循环, 那中断状态会被延续到下一轮的第3步检查中断,从而抛出中断异常
}
} finally {
lock.unlock();
}
}
Have you noticed that the first step is to acquire the lock first, because whether it is a counter operation, or nextGeneration
, breakBarrier
or entering a blocked state, it is guaranteed by the lock
In step 6, it may be interrupted while waiting, depending on the situation, whether it is an external interrupt or an internal interrupt: nextGeneration
、breakBarrier
6.1 If it is an external interrupt, destroy the fence and throw an exception.
6.2 If it is an internal interruption, it will only restore the interruption state and proceed to the next step. Because the general interruption belongs to the end of the wait, no exception will be thrown.
6.2 Subsequent judgment of timeout, destruction, and next generation (end or reset)
reset
Reset will break the current generation wait, so it will be called breakBarrier
.
Enter the next generation: nextGeneration
.
If the reset is only for the next generation, the latter can be called directly; but the waiting of the current generation must be interrupted.
So combine the two only different operations in the sum breakBarrier
:nextGeneration
generation.broken = true;
generation = new 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();
}
}
statistical methods
The main purpose is to judge whether it is damaged and to obtain the number of waiting threads.
Combined getParties
, you can also know the number of remaining unreached threads.
public boolean isBroken() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return generation.broken;
} finally {
lock.unlock();
}
}
public int getNumberWaiting() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return parties - count;
} finally {
lock.unlock();
}
}
public int getParties() {
return parties;
}
Note that the number of threads calling the method is inconsistent await
with the initial settingparties
- Less than
parties
, cannotcount
reduce to 0, will eventually cause all executionawait
threads to be blocked. - More than
parties
, justparties
after all arrived, entered the next generation. In the next generation, the number of threads is more thanparties
the number, causing this part of the thread to block.
It is recommended that the number of threads must be equal to the number of threads parties
, and try to call await
the method .
Summarize
The examples about CountDownLatch
and CyclicBarrier
are not very good, but they can barely convey the meaning.
To sum up, the three major crimes of CyclicBarrier
Juan's death CountDownLatch
:
-
Reuse is allowed, and can be reused after execution or manual reset;
CountDownLatch
directly scrapped. -
Callback logic is allowed to be executed, usually the last thread to reach the fence calls itself. We can use this feature to perform finishing touches in our business logic.
-
If there is a problem, the current generation can be destroyed (notify other threads), and reset for the next time. It is different from not caring about the result after
CountDownLatch
reaching the specified condition,CyclicBarrier
and must wait on the fence. If a worker thread gets an exception before it arrives, it will need to be manually reset.Of course, abnormalities, timeouts, etc. will automatically destroy the current use, but note that it cannot be used directly for the next time, and must be reset manually.