概述
CyclicBarrier
的字面意思是可循环使用(Cyclic)的屏障(Barrier) 。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。 CyclicBarrier
默认的构造方法是CyclicBarrier(int parties)
,其参数表示屏障拦截的线程数量 ,每个线程调用await方法告诉CyclicBarrier我已经到达了屏障,然后当前线程被阻塞。
CyclicBarrier
通常称为循环屏障。它和CountDownLatch
很相似,都可以使线程先等待然后再执行 。不过CountDownLatch是使一批线程等待另一批线程执行完后再执行 ;而CyclicBarrier只是使等待的线程达到一定数目后再让它们继续执行。故而CyclicBarrier内部也有一个计数器,计数器的初始值在创建对象时通过构造参数指定,如下所示:
java
public CyclicBarrier(int parties) {
this(parties, null);
}
每调用一次await()方法都将使阻塞的线程数+1,只有阻塞的线程数达到设定值时屏障才会打开,允许阻塞的所有线程继续执行
。除此之外,CyclicBarrier还有几点需要注意的地方:
- CyclicBarrier的计数器可以重置而CountDownLatch不行,这意味着CyclicBarrier实例可以被重复使用而CountDownLatch只能被使用一次。而这也是循环屏障循环二字的语义所在。
- CyclicBarrier允许用户自定义barrierAction操作,这是个可选操作,可以在创建CyclicBarrier对象时指定
java
public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException();
this.parties = parties;
this.count = parties;
this.barrierCommand = barrierAction;
}
一旦用户在创建CyclicBarrier对象时设置了barrierAction参数,则在阻塞线程数达到设定值屏障打开前,会调用barrierAction的run()方法完成用户自定义的操作。
使用方式
1、简单使用
公司组织旅游,大家都有经历过,10个人,中午到饭点了,需要等到10个人都到了才能开饭,先到的人坐那等着,代码如下
java
package 循环阑珊;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
public class demo11 {
//创建一个循环屏障
public static CyclicBarrier cyclicBarrier=new CyclicBarrier(10);
static class myThreads extends Thread{
int sleep;
public myThreads(String name,int sleep) {
super(name);
this.sleep=sleep;
}
@Override
public void run() {
try {
//模拟休眠
TimeUnit.SECONDS.sleep(sleep);
//计算等待的时间
long start = System.currentTimeMillis();
//调用await()的时候,当前线程将会被阻塞,需要等待其他员工都到达await了才能继续、
cyclicBarrier.await();
long end = System.currentTimeMillis();
//等待所有人都到了才会进行吃饭
System.out.println(this.getName()+",sleep:"+this.sleep+"等待了:"+(end-start)+"(ms)m,开始吃饭了");
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
//等待10个人都到齐了才开饭
for (int i = 1; i <=10 ; i++) {
new myThreads("员工"+i,i).start();
}
}
}
运行结果:
员工10,sleep:10等待了:0(ms)m,开始吃饭了 员工8,sleep:8等待了:2000(ms)m,开始吃饭了 员工7,sleep:7等待了:3001(ms)m,开始吃饭了 员工6,sleep:6等待了:4001(ms)m,开始吃饭了 员工5,sleep:5等待了:5001(ms)m,开始吃饭了 员工1,sleep:1等待了:9001(ms)m,开始吃饭了 员工3,sleep:3等待了:7001(ms)m,开始吃饭了 员工4,sleep:4等待了:6000(ms)m,开始吃饭了 员工2,sleep:2等待了:8001(ms)m,开始吃饭了 员工9,sleep:9等待了:1000(ms)m,开始吃饭了
上面模拟了10个员工上桌吃饭给的场景,等待所有员工都到齐了才能进行开饭。可以清除的看到,前面的所有人都在等待第10个员工的到来;上面调用了cyclicBarrier.await();
会让当前线程等待,当10个员工都调用了cyclicBarrier.await();
后,所有处于等待中的员工都会被唤醒,然后继续运行下面的吃饭动作;
2、重复使用CyclicBarrier
对示例1进行改造一下,吃饭完毕之后,所有人都去车上,待所有人都到车上之后,驱车去下一景点玩。
java
package 循环阑珊;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
public class demo22 {
//创建一个循环屏障
public static CyclicBarrier cyclicBarrier=new CyclicBarrier(10);
static class myThreads extends Thread{
int sleep;
public myThreads(String name,int sleep) {
super(name);
this.sleep=sleep;
}
void eat(){
//吃饭
try {
//模拟休眠
TimeUnit.SECONDS.sleep(sleep);
//计算等待的时间
long start = System.currentTimeMillis();
//调用await()的时候,当前线程将会被阻塞,需要等待其他员工都到达await了才能继续、
cyclicBarrier.await();
long end = System.currentTimeMillis();
System.out.println(this.getName()+",sleep:"+this.sleep+"等待了:"+(end-start)+"(ms)m,开始吃饭了");
//用来模拟吃饭的时间,先吃完的就去车上等待其他的员工
TimeUnit.SECONDS.sleep(sleep);
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}
void drive(){
//上车去下一个景点
try {
//计算等待的时间
long start = System.currentTimeMillis();
//调用await()的时候,当前线程将会被阻塞,需要等待其他员工都到达await了才能继续、
cyclicBarrier.await();
long end = System.currentTimeMillis();
System.out.println(this.getName()+",sleep:"+this.sleep+"等待了:"+(end-start)+"(ms)m,准备去到下一个景点");
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}
@Override
public void run() {
//先吃饭,等待所有人都到了才吃
eat();
//上车等待所有人都到了就前往下一个景点
drive();
}
}
public static void main(String[] args) {
//等待10个人都到齐了才开饭
for (int i = 1; i <=10 ; i++) {
new myThreads("员工"+i,i).start();
}
}
}
运行结果:
员工1,sleep:1等待了:9001(ms)m,开始吃饭了 员工6,sleep:6等待了:4000(ms)m,开始吃饭了 员工5,sleep:5等待了:5000(ms)m,开始吃饭了 员工3,sleep:3等待了:7000(ms)m,开始吃饭了 员工4,sleep:4等待了:6000(ms)m,开始吃饭了 员工10,sleep:10等待了:0(ms)m,开始吃饭了 员工2,sleep:2等待了:8001(ms)m,开始吃饭了 员工9,sleep:9等待了:1002(ms)m,开始吃饭了 员工7,sleep:7等待了:3001(ms)m,开始吃饭了 员工8,sleep:8等待了:2001(ms)m,开始吃饭了 员工10,sleep:10等待了:0(ms)m,准备去到下一个景点 员工2,sleep:2等待了:8000(ms)m,准备去到下一个景点 员工5,sleep:5等待了:5000(ms)m,准备去到下一个景点 员工7,sleep:7等待了:3000(ms)m,准备去到下一个景点 员工8,sleep:8等待了:2000(ms)m,准备去到下一个景点 员工9,sleep:9等待了:1000(ms)m,准备去到下一个景点 员工1,sleep:1等待了:9001(ms)m,准备去到下一个景点 员工6,sleep:6等待了:4000(ms)m,准备去到下一个景点 员工3,sleep:3等待了:7000(ms)m,准备去到下一个景点 员工4,sleep:4等待了:6000(ms)m,准备去到下一个景点
可以看到代码中CyclicBarrier相当于使用了2次,第一次用于等待所有人到达后开饭,第二次用于等待所有人上车后驱车去下一景点。注意一些先到的员工会在其他人到达之前,都处于等待状态(cyclicBarrier.await();会让当前线程阻塞),无法干其他事情,等到最后一个人到了会唤醒所有人,然后继续。
3、最后到的人给大家上酒,然后开饭
员工10是最后到达的,让所有人都久等了,那怎么办,得给所有人倒酒,然后开饭,代码如下:
java
package 循环阑珊;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
public class demo33 {
//创建一个循环屏障,并且添加barrierAction
public static CyclicBarrier cyclicBarrier=new CyclicBarrier(10,()->{
//模拟倒酒也花费了2秒钟
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "说,不好意思,让大家久等了,给大家倒酒赔罪!");
});
static class myThreads extends Thread{
int sleep;
public myThreads(String name,int sleep) {
super(name);
this.sleep=sleep;
}
void eat(){
//吃饭
try {
//模拟休眠
TimeUnit.SECONDS.sleep(sleep);
//计算等待的时间
long start = System.currentTimeMillis();
//调用await()的时候,当前线程将会被阻塞,需要等待其他员工都到达await了才能继续、
cyclicBarrier.await();
long end = System.currentTimeMillis();
System.out.println(this.getName()+",sleep:"+this.sleep+"等待了:"+(end-start)+"(ms)m,开始吃饭了");
//用来模拟吃饭的时间,先吃完的就去车上等待其他的员工
TimeUnit.SECONDS.sleep(sleep);
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}
void drive(){
//上车去下一个景点
try {
//计算等待的时间
long start = System.currentTimeMillis();
//调用await()的时候,当前线程将会被阻塞,需要等待其他员工都到达await了才能继续、
cyclicBarrier.await();
long end = System.currentTimeMillis();
System.out.println(this.getName()+",sleep:"+this.sleep+"等待了:"+(end-start)+"(ms)m,准备去到下一个景点");
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}
@Override
public void run() {
//先吃饭,等待所有人都到了才吃
eat();
//上车等待所有人都到了就前往下一个景点
drive();
}
}
public static void main(String[] args) {
//等待10个人都到齐了才开饭
for (int i = 1; i <=10 ; i++) {
new myThreads("员工"+i,i).start();
}
}
}
运行结果:
员工10说,不好意思,让大家久等了,给大家倒酒赔罪! 员工10,sleep:10等待了:2001(ms)m,开始吃饭了 员工1,sleep:1等待了:11000(ms)m,开始吃饭了 员工2,sleep:2等待了:10001(ms)m,开始吃饭了 员工7,sleep:7等待了:5000(ms)m,开始吃饭了 员工8,sleep:8等待了:4000(ms)m,开始吃饭了 员工6,sleep:6等待了:6000(ms)m,开始吃饭了 员工5,sleep:5等待了:7000(ms)m,开始吃饭了 员工4,sleep:4等待了:8001(ms)m,开始吃饭了 员工3,sleep:3等待了:9001(ms)m,开始吃饭了 员工9,sleep:9等待了:3000(ms)m,开始吃饭了 员工10说,不好意思,让大家久等了,给大家倒酒赔罪! 员工10,sleep:10等待了:2001(ms)m,准备去到下一个景点 员工1,sleep:1等待了:11001(ms)m,准备去到下一个景点 员工2,sleep:2等待了:10001(ms)m,准备去到下一个景点 员工4,sleep:4等待了:7999(ms)m,准备去到下一个景点 员工3,sleep:3等待了:9000(ms)m,准备去到下一个景点 员工8,sleep:8等待了:4000(ms)m,准备去到下一个景点 员工9,sleep:9等待了:2999(ms)m,准备去到下一个景点 员工7,sleep:7等待了:5000(ms)m,准备去到下一个景点 员工6,sleep:6等待了:6001(ms)m,准备去到下一个景点 员工5,sleep:5等待了:7000(ms)m,准备去到下一个景点
代码中创建CyclicBarrier
对象时,多传入了一个参数(内部是倒酒操作),先到的人先等待,待所有人都到齐之后,需要先给大家倒酒,然后唤醒所有等待中的人让大家开饭。从输出结果中我们发现,倒酒操作是由最后一个人操作的,最后一个人倒酒完毕之后,才唤醒所有等待中的其他员工,让大家开饭。
4、其中一个人等待中被打断了
员工5等待中,突然接了个电话,有点急事,然后就拿起筷子开吃了,其他人会怎么样呢?看着他吃么?
java
public static void main(String[] args) throws InterruptedException {
for (int i = 1; i <= 10; i++) {
int sleep = 0;
if (i == 10) {
sleep = 10;
}
T t = new T("员工" + i, sleep);
t.start();
if (i == 5) {
//模拟员工5接了个电话,将自己等待吃饭给打断了
TimeUnit.SECONDS.sleep(1);
System.out.println(t.getName() + ",有点急事,我先开干了!");
t.interrupt();
TimeUnit.SECONDS.sleep(2);
}
}
}
java
员工2到了!
员工4到了!
员工1到了!
员工3到了!
员工5到了!
员工5,有点急事,我先开干了!
员工5,sleep:0 等待了1001(ms),开始吃饭了!
员工1,sleep:0 等待了1001(ms),开始吃饭了!
员工2,sleep:0 等待了1001(ms),开始吃饭了!
员工4,sleep:0 等待了1001(ms),开始吃饭了!
员工3,sleep:0 等待了1001(ms),开始吃饭了!
java.lang.InterruptedException
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.reportInterruptAfterWait(AbstractQueuedSynchronizer.java:2014)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2048)
at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:234)
at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:362)
at 循环阑珊.demo55$T.run(demo55.java:28)
java.util.concurrent.BrokenBarrierException
at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:250)
at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:362)
at 循环阑珊.demo55$T.run(demo55.java:28)
java.util.concurrent.BrokenBarrierException
at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:250)
at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:362)
at 循环阑珊.demo55$T.run(demo55.java:28)
java.util.concurrent.BrokenBarrierException
at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:250)
at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:362)
at 循环阑珊.demo55$T.run(demo55.java:28)
java.util.concurrent.BrokenBarrierException
at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:250)
at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:362)
at 循环阑珊.demo55$T.run(demo55.java:28)
java.util.concurrent.BrokenBarrierException
at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:207)
at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:362)
at 循环阑珊.demo55$T.run(demo55.java:28)
java.util.concurrent.BrokenBarrierException
at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:207)
at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:362)
at 循环阑珊.demo55$T.run(demo55.java:28)
java.util.concurrent.BrokenBarrierException
at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:207)
at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:362)
at 循环阑珊.demo55$T.run(demo55.java:28)
java.util.concurrent.BrokenBarrierException
at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:207)
at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:362)
at 循环阑珊.demo55$T.run(demo55.java:28)
员工6到了!
员工8到了!
员工6,sleep:0 等待了0(ms),开始吃饭了!
员工7到了!
员工9到了!
员工8,sleep:0 等待了1(ms),开始吃饭了!
员工9,sleep:0 等待了0(ms),开始吃饭了!
员工7,sleep:0 等待了1(ms),开始吃饭了!
员工10到了!
员工10,sleep:10 等待了0(ms),开始吃饭了!
java.util.concurrent.BrokenBarrierException
at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:207)
at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:362)
at 循环阑珊.demo55$T.run(demo55.java:28)
输出的信息看着有点乱,这样说:
- 员工5遇到急事,拿起筷子就是吃,这样好么,当然不好,他这么做了,后面看他这么做了都跟着这么做(这种场景是不是很熟悉,有一个人拿起筷子先吃起来,其他人都跟着上了),直接不等其他人了,拿起筷子就开吃了。
CyclicBarrier
遇到这种情况就是这么处理的。前面4个员工都在await()处等待着,员工5也在await()上等待着,等了1秒(TimeUnit.SECONDS.sleep(1);),接了个电话,然后给员工5发送中断信号后(t.interrupt();),员工5的await()方法会触发InterruptedException
异常,- 此时其他等待中的前4个员工,看着5开吃了,自己立即也不等了,内部从await()方法中触发
BrokenBarrierException
异常 - 然后也开吃了,后面的6/7/8/9/10员工来了以后发现大家都开吃了,自己也不等了,6-10员工调用await()直接抛出了
BrokenBarrierException
异常,然后继续向下。
结论:
- 内部有一个人把规则破坏了(接收到中断信号),其他人都不按规则来了,不会等待了
- 接收到中断信号的线程,await方法会触发
InterruptedException
异常,然后被唤醒向下运行 - 其他等待中或者后面到达的线程,会在await()方法上触发
BrokenBarrierException
异常,然后继续执行
5、重建规则
员工1等待5秒超时之后,开吃了,打破了规则,先前等待中的以及后面到达的都不按规则来了,都拿起筷子开吃。过了一会,导游重新告知大家,要按规则来,然后重建了规则,大家都按规则来了。
java
public static CyclicBarrier cyclicBarrier = new CyclicBarrier(10);
//规则是否已重建
public static boolean guizhe = false;
public static class T extends Thread {
int sleep;
public T(String name, int sleep) {
super(name);
this.sleep = sleep;
}
@Override
public void run() {
long starTime = 0, endTime = 0;
try {
//模拟休眠
TimeUnit.SECONDS.sleep(sleep);
starTime = System.currentTimeMillis();
//调用await()的时候,当前线程将会被阻塞,需要等待其他员工都到达await了才能继续
System.out.println(this.getName() + "到了!");
if (!guizhe) {
if (this.getName().equals("员工1")) {
cyclicBarrier.await(5, TimeUnit.SECONDS);
} else {
cyclicBarrier.await();
}
} else {
cyclicBarrier.await();
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
endTime = System.currentTimeMillis();
System.out.println(this.getName() + ",sleep:" + this.sleep + " 等待了" + (endTime - starTime) + "(ms),开始吃饭了!");
}
}
public static void main(String[] args) throws InterruptedException {
for (int i = 1; i <= 10; i++) {
T t = new T("员工" + i, i);
t.start();
}
//等待10秒之后,重置,重建规则
TimeUnit.SECONDS.sleep(15);
cyclicBarrier.reset();
guizhe = true;
System.out.println("---------------大家太皮了,请大家按规则来------------------");
//再来一次
for (int i = 1; i <= 10; i++) {
T t = new T("员工" + i, i);
t.start();
}
}
}
运行结果:
java
员工1到了!
员工2到了!
员工3到了!
员工4到了!
员工5到了!
员工6到了!
java.util.concurrent.TimeoutException
at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:257)
at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:435)
at com.java.thread.demo.cyclicbarrier.CyclicBarrierDemo6$T.run(CyclicBarrierDemo6.java:40)
员工1,sleep:1 等待了5004(ms),开始吃饭了!
java.util.concurrent.BrokenBarrierException
at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:250)
at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:362)
at com.java.thread.demo.cyclicbarrier.CyclicBarrierDemo6$T.run(CyclicBarrierDemo6.java:42)
java.util.concurrent.BrokenBarrierException员工4,sleep:4 等待了2000(ms),开始吃饭了!
at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:250)
at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:362)
at com.java.thread.demo.cyclicbarrier.CyclicBarrierDemo6$T.run(CyclicBarrierDemo6.java:42)
java.util.concurrent.BrokenBarrierException
at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:250)
at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:362)
at com.java.thread.demo.cyclicbarrier.CyclicBarrierDemo6$T.run(CyclicBarrierDemo6.java:42)
员工6,sleep:6 等待了2(ms),开始吃饭了!
员工3,sleep:3 等待了3005(ms),开始吃饭了!
java.util.concurrent.BrokenBarrierException
at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:250)
at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:362)
at com.java.thread.demo.cyclicbarrier.CyclicBarrierDemo6$T.run(CyclicBarrierDemo6.java:42)
java.util.concurrent.BrokenBarrierException
at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:250)
员工5,sleep:5 等待了1002(ms),开始吃饭了!
at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:362)
at com.java.thread.demo.cyclicbarrier.CyclicBarrierDemo6$T.run(CyclicBarrierDemo6.java:42)
员工2,sleep:2 等待了4003(ms),开始吃饭了!
员工7到了!
java.util.concurrent.BrokenBarrierException
at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:207)
at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:362)
at com.java.thread.demo.cyclicbarrier.CyclicBarrierDemo6$T.run(CyclicBarrierDemo6.java:42)
员工7,sleep:7 等待了1(ms),开始吃饭了!
员工8到了!
java.util.concurrent.BrokenBarrierException
at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:207)
at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:362)
at com.java.thread.demo.cyclicbarrier.CyclicBarrierDemo6$T.run(CyclicBarrierDemo6.java:42)
员工8,sleep:8 等待了0(ms),开始吃饭了!
员工9到了!
java.util.concurrent.BrokenBarrierException
at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:207)
at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:362)
at com.java.thread.demo.cyclicbarrier.CyclicBarrierDemo6$T.run(CyclicBarrierDemo6.java:42)
员工9,sleep:9 等待了0(ms),开始吃饭了!
员工10到了!
java.util.concurrent.BrokenBarrierException
at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:207)
at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:362)
at com.java.thread.demo.cyclicbarrier.CyclicBarrierDemo6$T.run(CyclicBarrierDemo6.java:42)
员工10,sleep:10 等待了1(ms),开始吃饭了!
---------------大家太皮了,请大家按规则来------------------
员工1到了!
员工2到了!
员工3到了!
员工4到了!
员工5到了!
员工6到了!
员工7到了!
员工8到了!
员工9到了!
员工10到了!
员工10,sleep:10 等待了0(ms),开始吃饭了!
员工2,sleep:2 等待了7997(ms),开始吃饭了!
员工1,sleep:1 等待了8999(ms),开始吃饭了!
员工4,sleep:4 等待了5997(ms),开始吃饭了!
员工5,sleep:5 等待了5000(ms),开始吃饭了!
员工7,sleep:7 等待了2997(ms),开始吃饭了!
员工8,sleep:8 等待了1998(ms),开始吃饭了!
员工3,sleep:3 等待了6999(ms),开始吃饭了!
员工9,sleep:9 等待了1000(ms),开始吃饭了!
员工6,sleep:6 等待了4001(ms),开始吃饭了!
第一次规则被打乱了,过了一会导游重建了规则(cyclicBarrier.reset();
),接着又重来来了一次模拟等待吃饭的操作,正常了。
总结
上面的几个例子也充分说明了CyclicBarrier
:
- 可以等待所有人都到齐了才进行下一步操作
- 可以重复使用
- 可以添加一个自定义
barrierAction
操作 - 当一个人被中断后是会打破规则的
- 可以重建规则
源码解析
CyclicBarrier内部使用了ReentrantLock和Condition两个类来保证同步
初始化
java
public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException();
//对应的线程个数
this.parties = parties;
//当前未阻塞的线程个数
this.count = parties;
//执行的线程
this.barrierCommand = barrierAction;
}
private static class Generation {
//n+1个线程中只要有一个被中断或者出现超时,就将broken设置为true
boolean broken = false;
}
//独占锁
private final ReentrantLock lock = new ReentrantLock();
//因为CyclicBarrier是重复可利用的,一轮代表一代
private Generation generation = new Generation();
//当调用await后,会被阻塞。
private final Condition trip = lock.newCondition();
初始化代码调用的构造方法需要传入一个count和一个Runnable对象。count对应着线程数量,Runnable对应着最后待同步的线程!
await()
CyclicBarrier是可循环利用的,每个循环对应着一个年代,对应着CyclicBarrier就内置了一个Generation类,用来描述当前年代的状态。
java
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 {
//初始化一个Generation
final Generation g = generation;
//有线程超时了,或者是被中断了
if (g.broken)
throw new BrokenBarrierException();
//如果线程被中断了,将所有线程唤醒,可中断
if (Thread.interrupted()) {
//唤醒所有等待的线程,重置count,并将g.broken=true
breakBarrier();
//抛出异常
throw new InterruptedException();
}
//await后调用后,count--
int index = --count;
if (index == 0) { //count==0,说明所有线程都调用await()了,可以执行barrierCommand了。
//是否执行最后线程了
boolean ranAction = false;
try {
final Runnable command = barrierCommand;
if (command != null)
//开始执行
command.run();
//执行最后线程了
ranAction = true;
//唤醒所有等待在trip中condition queue中的线程,开启新一轮线程同步
nextGeneration();
return 0;
} finally {
//如果执行ranAction失败,说明出现异常
if (!ranAction)
//上述代码出现异常,执行breakBarrier
breakBarrier();
}
}
//运行到这里的代码,都是n个线程中的n-1个线程
for (;;) {
try {
//阻塞式调用
if (!timed)
//阻塞将线程挂起到condition queue
trip.await();
//调用的是await(timeout),设置的有超时时间
else if (nanos > 0L)
//最多阻塞nanos时间将线程挂起到condition queue
nanos = trip.awaitNanos(nanos);
} catch (InterruptedException ie) {
//在等待的过程中,如果线程被中断,如果其他线程还未调用breakBarrier,那么当前线程就调用
if (g == generation && ! g.broken) {
breakBarrier();
throw ie;
} else {
//如果其他线程已经breakBarrier,就直接自我中断就ok了
Thread.currentThread().interrupt();
}
}
//如果正常流程被打断,抛出BrokenBarrierException
if (g.broken)
throw new BrokenBarrierException();
//如果正常的更新换代的话,返回index
if (g != generation)
return index;
//如果await超时的话,也会breakBarrier,抛出TimeoutException
if (timed && nanos <= 0L) {
breakBarrier();
throw new TimeoutException();
}
}
} finally {
lock.unlock();
}
}
private void breakBarrier() {
//n+1个线程当中有线程被中断,或者await请求超时了,就将当前这个代打断,唤醒所有线程,各个线程自己单独处理这个异常
//设置broken为true
generation.broken = true;
//重置count
count = parties;
// 唤醒等待在trip的condition queue中的全部线程
trip.signalAll();
}
private void nextGeneration() {
//当前这轮线程全部执行成功,重置年代,开始下一轮同步
// 唤醒等待在trip的condition queue中的全部线程
trip.signalAll();
// count 重置为 parties
count = parties;
//generation也重新初始化
generation = new Generation();
}
可以看到CyclicBarrier的底层是通过一个ReentrantLock、一个int变量count、一个Condition、一个Generation来实现的。
基本流程:
- 首先初始化一个
ReentranLock
对象,将parties赋值给变量count,初始化一个Condition,初始化一个Generation,Generation只有一个broken布尔值对应着当前的同步状态是否被破坏,将Runnable对象保存给barrierCommad。 - 当线程调用await()的时候,会先抢夺lock锁,然后将count--,如果count==0.说明线程都已经执行完毕了,可以开始执行barrierCommand了,如果有就执行,并且通过SingalAll()来唤醒所有等待的线程,并且将Generation重新初始化,将count重新赋值为parties,开始下一轮的同步。
- 如果count!=0,说明还有其他的线程还没有完成,就调用conditon.awiat()方法
注意:Generation.borken
当有线程被中断或者线程被挂起超时的时候,将generation的broken设置为true,代表同步被破坏了,会立即唤醒所有之前挂起的线程,新来的线程会在操作count--之前,就判断broken,如果broken=true,就会直接抛出异常
和CountDownLatch的区别
相同点
- 二者都能让一个或多个线程阻塞等待,都可以用在多个线程间的协调,起到线程同步的作用。
不同点
CountDownLatch
的计数器只能使用一次,而CyclicBarrier
的计数器可以反复 使用。CountDownLatch.await
一般阻塞工作线程,所有的进行预备工作的线程执行countDown
,而 CyclicBarrier 通过工作线程调用 await 从而自行阻塞,直到所有工作线程达到指定屏障,所有的线程才会返回各自执行自己的工作。CyclicBarrier
还可以提供一个barrierAction
,合并多线程计算结果。CountDownLatch
会阻塞主线程,CyclicBarrier
不会阻塞主线程,只会阻塞子线程。