突击并发编程JUC系列演示代码地址:
https://github.com/mtcarpenter/JavaTutorial
俗话说趁热要打铁,上篇中介绍的 CountDownLatch
的根本用法, CountDownLatch
计数器是一次性的,也就是等到计数器值变为0后,再调用CountDownLatch
的await
和countdown
办法都会立即返回,这就起不到线程同步的成果了。
对于局部业务须要屡次循环应用,就能够应用本章节的 CyclicBarrier
,CyclicBarrier
的字面意思是可循环应用(Cyclic
)的屏障(Barrier
), 它同样领有 CountDownLatch
的性能,CyclicBarrier
的字面意思是可循环应用(Cyclic
)的屏障(Barrier
)。它要做的事件是,让一组线程达到一个屏障(也能够叫同步点)时被阻塞,直到最初一个线程达到屏障时,屏障才会开门,所有被屏障拦挡的线程才会持续运行。
重要办法
结构参数
CyclicBarrier(int parties)
:parties
示意的是参加的线程个数,这个数字通过构造方法进行传递。CyclicBarrier(int parties, Runnable barrierAction)
: 能够承受一个Runnable
参数 ,此参数示意栅栏动作,当所有线程达到栅栏后,在所有线程执行下一步动作前,运行参数中的动作,这个动作由最初一个达到栅栏的线程执行。
await()
await()
: 以后线程调用CyclicBarrier
的该办法时会被阻塞,直到满足上面条件之一才会返回:parties
个线程都调用了await()
办法,也就是线程都到了屏障点;其余线程调用了以后线程的interrupt()
办法中断了以后线程,则以后线程会抛出InterruptedException
异样而返回;与以后屏障点关联的Generation
对象的broken
标记被设置为true
时,会抛出BrokenBarrierException
异样,而后返回。await(long timeout, TimeUnit unit)
: 以后线程调用CyclicBarrier
的该办法时会被阻塞,直到满足上面条件之一才会返回:parties
个线程都调用了await()
办法,也就是线程都到了屏障点,这时候返回true
;设置的超时工夫到了后返回false
;其余线程调用以后线程的interrupt()
办法中断了以后线程,则以后线程会抛出InterruptedException
异样而后返回;与以后屏障点关联的Generation
对象的broken
标记被设置为true
时,会抛出BrokenBarrierException
异样,而后返回。
案例上手
分组期待
跟后面countDownLatch
一样通过学生的案例进行解说,新日小学的同学全副已在操场上,然而操场的进口的只有三个,进口同时只能包容三个年级,先整顿好的三个年级为一组先出,前面的年级为另一组进行出场,示例如下:
public class CyclicBarrierExample1 { private final static int gradeNum = 6; private static CyclicBarrier barrier = new CyclicBarrier(3); public static void main(String[] args) throws Exception { ExecutorService exec = Executors.newScheduledThreadPool(gradeNum); System.out.println("告诉、告诉,请筹备的年级先登程....."); for (int i = 0; i < gradeNum; i++) { TimeUnit.SECONDS.sleep(1); int gradeName = i + 1; exec.submit(() -> { try { wait(gradeName); } catch (Exception e) { } }); } exec.shutdown(); } private static void wait(int gradeName) throws Exception { TimeUnit.SECONDS.sleep(1); System.out.println(gradeName + "年级所有同学来到了进口......"); barrier.await(); System.out.println(gradeName + "年级所有同学到登程"); }}
每个子工作在执行完本人的逻辑后会调用await
办法。一开始计数器值为 3 ,相当于三个班级,当第一个线程调用await
办法时,计数器值会递加为 1。因为此时计数器值不为 0,所以以后线程就到了屏障点而被阻塞。而后第二个线程调用await
时,会进入屏障,计数器值也会递加,当初计数器值为 0,执行结束后退出屏障点,持续向下运行。
运行后果如下:
告诉、告诉,请筹备的年级先登程.....1年级所有同学来到了进口......2年级所有同学来到了进口......3年级所有同学来到了进口......3年级所有同学到登程1年级所有同学到登程2年级所有同学到登程4年级所有同学来到了进口......5年级所有同学来到了进口......6年级所有同学来到了进口......6年级所有同学到登程5年级所有同学到登程4年级所有同学到登程
超时期待
为了早日达到植树场地,学校领导规定每一个年级从操场进来的工夫为 2 秒,对于超时的引起的异样,再进行异样解决,示例如下
public class CyclicBarrierExample2 { private final static int gradeNum = 6; private static CyclicBarrier barrier = new CyclicBarrier(3); public static void main(String[] args) throws Exception { ExecutorService exec = Executors.newScheduledThreadPool(gradeNum); System.out.println("告诉、告诉,请筹备的年级先登程....."); for (int i = 0; i < gradeNum; i++) { TimeUnit.SECONDS.sleep(1); int gradeName = i + 1; exec.submit(() -> { try { wait(gradeName); } catch (Exception e) { } }); } exec.shutdown(); } private static void wait(int gradeName) throws Exception { TimeUnit.SECONDS.sleep(1); System.out.println(gradeName + "年级所有同学来到了进口......"); try { barrier.await(2000, TimeUnit.MILLISECONDS); } catch (Exception e) { System.out.println("CyclicBarrier 超时异样: " + gradeName + "年级-" + e); } System.out.println(gradeName + "年级所有同学到登程"); }}
与下面的例子相比,CyclicBarrier
能够设置超时工夫, 如barrier.await(2000, TimeUnit.MILLISECONDS);
子线程超过两秒,就抛出异样,依据本人的业务是中断还是持续向下运行。
运行后果如下:
告诉、告诉,请筹备的年级先登程.....1年级所有同学来到了进口......2年级所有同学来到了进口......3年级所有同学来到了进口......3年级所有同学到登程1年级所有同学到登程2年级所有同学到登程4年级所有同学来到了进口......5年级所有同学来到了进口......6年级所有同学来到了进口......CyclicBarrier 超时异样: 4年级-java.util.concurrent.TimeoutException4年级所有同学到登程CyclicBarrier 超时异样: 5年级-java.util.concurrent.BrokenBarrierException5年级所有同学到登程CyclicBarrier 超时异样: 6年级-java.util.concurrent.BrokenBarrierException6年级所有同学到登程
回调
每一个年级达到入口之后,汇报给领导,领导进行接下来的安顿。示例如下:
public class CyclicBarrierExample3 { private final static int gradeNum = 6; private static CyclicBarrier barrier = new CyclicBarrier(3, new Runnable() { @Override public void run() { System.out.println("******所有子线程达到屏障******"); } }); public static void main(String[] args) throws Exception { ExecutorService exec = Executors.newScheduledThreadPool(gradeNum); System.out.println("告诉、告诉,请筹备的年级先登程....."); for (int i = 0; i < gradeNum; i++) { TimeUnit.SECONDS.sleep(1); int gradeName = i + 1; exec.submit(() -> { try { wait(gradeName); } catch (Exception e) { } }); } exec.shutdown(); } private static void wait(int gradeName) throws Exception { TimeUnit.SECONDS.sleep(1); System.out.println(gradeName + "年级所有同学来到了进口......"); barrier.await(); System.out.println(gradeName + "年级所有同学到登程"); }}
如上代码创立了一个 CyclicBarrier
对象,其第一个参数为计数器初始值,第二个参数Runable
是当计数器值为 0 是须要执行的工作。当计数器值为 0,这时就会去执行CyclicBarrier
构造函数中的工作,执行结束后退出屏障点,持续向下运行。
CyclicBarrier
与CountDownLatch
区别
CyclicBarrier
与CountDownLatch
可能容易混同,咱们强调下它们的区别。
CountDownLatch
的参加线程是有不同角色的,有的负责倒计时,有的在期待倒计时变为 0,负责倒计时和期待倒计时的线程都能够有多个,用于不同角色线程间的同步。CyclicBarrier
的参加线程角色是一样的,用于同一角色线程间的协调一致。CountDownLatch
是一次性的,而CyclicBarrier
是能够反复利用的。
欢送关注公众号 山间木匠 , 我是小春哥,从事 Java 后端开发,会一点前端、通过继续输入系列技术文章以文会友,如果本文能为您提供帮忙,欢送大家关注、 点赞、分享反对,_咱们下期再见!_