突击并发编程 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.TimeoutException
4 年级所有同学到登程
CyclicBarrier 超时异样: 5 年级 -java.util.concurrent.BrokenBarrierException
5 年级所有同学到登程
CyclicBarrier 超时异样: 6 年级 -java.util.concurrent.BrokenBarrierException
6 年级所有同学到登程
回调
每一个年级达到入口之后,汇报给领导,领导进行接下来的安顿。示例如下:
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 后端开发,会一点前端、通过继续输入系列技术文章以文会友,如果本文能为您提供帮忙,欢送大家关注、点赞、分享反对,_咱们下期再见!_