文章目录
简单概述
CyclicBarrier 是规定了多少个线程都到达某个位置后,才会把这些线程放行。
举了例子,工作中我们都会有开会的场景,比如说我们需要等所有人都到齐才把会开起来,CyclicBarrier 就提供了模拟这种场景的工具供我们使用。
在讲解 CyclicBarrier 之前,先要讲一下 CyclicBarrier 和 CountDownLatch 的区别,他们最大的区别就是 CyclicBarrier 是一组线程,一组线程的去执行,而 CountDownLatch 是一次性的,不能循环往复的使用。
CyclicBarrier 就是循环往复的使用,你可以理解成,今天八点开会,明天八点继续开会。
CountDownLatch 是今天八点开会,只开一次,后面就不开了。
CyclicBarrier 创建
CyclicBarrier 在创建时,有两种构造函数,第一种构造函数中有一个参数,参数是一个数字,这个数字代表有多少个线程都到达指定位置后,才开始走后续的操作,代码如下:
CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
第二种构造函数是有两个参数,第一个参数是一个数字,第二个参数是一个Runnable,数字跟上述一样,代表多少个线程都到达指定位置后才开始走后续的操作,而第二个参数Runnable是最后一个到达的线程做哪些事情,代码如下:
CyclicBarrier cyclicBarrier = new CyclicBarrier(3, new Runnable() {
@Override
public void run() {
System.out.println("最后一个线程去做的事情");
}
});
其实 CyclicBarrier 创建时给的数字就是一个计数器,告诉 CyclicBarrier 你最多能拦截多少个线程,多余的线程等待。
CyclicBarrier 函数
现在我们知道了怎么创建,下面我们讲几个常用的函数,以下的几个函数讲解,你不妨先把代码示例放在你的工具中跑一下,这样能够快速明白这个函数的意思和用处。
await()
await() 函数是当某个线程到达指定位置后报告给 CyclicBarrier,告诉 CyclicBarrier 我已经到达指定位置,并已经在这个位置等待其他线程,当规定的线程数都到后,CyclicBarrier 就会放行所有的线程。
内部原理是调用 await() 函数后,CyclicBarrier 内部中会将计数器的数量减一,当计数器到达零后,所有的线程才会执行。
代码示例:
public static void main(String[] args) throws InterruptedException {
CyclicBarrier cyclicBarrier = new CyclicBarrier(2);
new Thread(() -> {
System.out.println("一号 到达");
try {
cyclicBarrier.await();
System.out.println("等所有线程都到达后, 一号执行的事情");
} catch (Exception e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
System.out.println("二号 到达");
try {
cyclicBarrier.await();
System.out.println("等所有线程都到达后, 二号执行的事情");
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
执行结果:
一号 到达
二号 到达
等所有线程都到达后, 二号执行的事情
等所有线程都到达后, 一号执行的事情
await(long timeout, TimeUnit unit)
await(long timeout, TimeUnit unit) 是某一个线程最多等待多长时间,如果等待时间超过了设定的时间就推翻 CyclicBarrier 并抛出异常。
推翻在这里做一下解释,引用上面简单概述的例子来讲,就是领导在开会地点等待过程中,因为等待时间太长,就把此次会议解散,去做其他的事情了,而没有到的那个人等后续到达了开会地点,发现开会的人都不在这里了,就也去做其他事情了。
根据下面的代码案例和打印结果,就能看到因为某个线程因为等待时间过长,就推翻了 CyclicBarrier,另一个线程在后续到达指定地方后直接抛出异常的结果。
另外需要注意的是,下面示例中一号线程抛出的异常是TimeoutException
,二号线程抛出的是BrokenBarrierException
。
代码示例:
public static void main(String[] args) throws InterruptedException {
CyclicBarrier cyclicBarrier = new CyclicBarrier(2);
new Thread(() -> {
System.out.println("一号 到达");
try {
// 一号线程只等待一秒
cyclicBarrier.await(1000, TimeUnit.MILLISECONDS);
System.out.println("等所有线程都到达后, 一号执行的事情");
} catch (Exception e) {
e.printStackTrace();
System.out.println("一号 因为等待时间太久, 推翻了 CyclicBarrier");
}
}).start();
new Thread(() -> {
System.out.println("二号 到达");
try {
// 二号线程等待两秒
Thread.sleep(2000);
cyclicBarrier.await();
System.out.println("等所有线程都到达后, 二号执行的事情");
} catch (Exception e) {
e.printStackTrace();
System.out.println("二号 因为同组线程推翻了 CyclicBarrier 抛出了异常");
}
}).start();
}
执行结果:
一号 到达
二号 到达
java.util.concurrent.TimeoutException
at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:257)
at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:435)
at com.user.test.CyclicBarrierTest.lambda$main$0(CyclicBarrierTest.java:51)
at java.lang.Thread.run(Thread.java:748)
一号 因为等待时间太久, 推翻了 CyclicBarrier
java.util.concurrent.BrokenBarrierException
at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:207)
at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:362)
at com.user.test.CyclicBarrierTest.lambda$main$1(CyclicBarrierTest.java:65)
at java.lang.Thread.run(Thread.java:748)
二号 因为同组线程推翻了 CyclicBarrier 抛出了异常
isBroken()
isBroken() 是判断 CyclicBarrier 是否被推翻。
引用讲解 await(long timeout, TimeUnit unit) 的例子,就能知道这个函数的作用。
reset()
reset() 函数是重置 CyclicBarrier 回到初始状态。
用下面的代码示例来讲一下这个函数的意思,先是创建了一个有 2 个线程数的 CyclicBarrier,然后一号线程开始启动,让一号线程最多等待时间是一秒钟,如果一秒钟以内其他线程未到指定地点,推翻 CyclicBarrier 并抛出异常,而在异常处理中我再重置这个 CyclicBarrier,这里你可以理解为,一个人去参加某活动,但是这个活动需要两个人一块参加,先到的人需要等后面的人补上,凑成两个人才能走,但是因为第一个先到的人因为耐不住性子,等待时间久了以后就走掉了,让出了这个位子。
代码示例:
public static void main(String[] args) throws InterruptedException {
// 创建一个只有2个线程数量的 CyclicBarrier
CyclicBarrier cyclicBarrier = new CyclicBarrier(2);
new Thread(() -> {
System.out.println("一号 到达");
try {
// 一号线程只等待一秒
cyclicBarrier.await(1000, TimeUnit.MILLISECONDS);
System.out.println("等所有线程都到达后, 一号执行的事情");
} catch (Exception e) {
e.printStackTrace();
// 让出活动位置
cyclicBarrier.reset();
System.out.println("一号 因为嫌等待时间太久, 让出了活动位置");
}
}).start();
new Thread(() -> {
try {
// 二号线程等待两秒
Thread.sleep(2000);
System.out.println("二号 到达");
cyclicBarrier.await();
System.out.println("二号参加活动");
} catch (Exception e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
try {
// 三号线程等待两秒
Thread.sleep(2000);
System.out.println("三号 到达");
cyclicBarrier.await();
System.out.println("三号参加活动");
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
执行结果:
一号 到达
java.util.concurrent.TimeoutException
at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:257)
at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:435)
at com.user.test.CyclicBarrierTest.lambda$main$0(CyclicBarrierTest.java:52)
at java.lang.Thread.run(Thread.java:748)
一号 因为嫌等待时间太久, 让出了活动位置
二号 到达
三号 到达
三号参加活动
二号参加活动