CyclicBarrier是java推出的一个并发编程工具,它用在多个线程之间协同工作。线程约定到达某个点,到达这个点之后的线程都停下来,直到最后一个线程也到达了这个点之后,所有的线程才会得到释放。常用的场景是:多个worker线程,每个线程都在循环地做一部分工作,并在最后用cyclicBarrier.await()设下约定点,当最后一个线程做完了工作也到达约定点后,所有线程得到释放,开始下一轮工作。也就是下面这样:

 while(!done()){
//working
cyclicBarrier.await();
}

CyclicBarrier还支持一个回调函数,每当一轮工作结束后,下一轮工作开始前,这个回调函数都会被调用一次。

但是,使用CyclicBarrier必须准守最佳实践的使用方法,否则,就可能达不到想要的效果。比如,下面这样,就是一种典型的错误使用方法:

    private void process(CyclicBarrier cyclicBarrier) {
final int n = 100;
Runnable worker= new Runnable() {
@Override
public void run() { try {
//模拟工作
Thread.sleep(3000);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
try {
cyclicBarrier.await();
} catch (BrokenBarrierException | InterruptedException ex) {
ex.printStackTrace();
}
}
System.out.println("Worker is done");
System.out.println("Thread of Worker is "+ Thread.currentThread().getId()); }; for (int i = 0; i < n; i++) {
Thread t1 = new Thread(worker);
Thread t2 = new Thread(worker);
t1.start();
t2.start();
} }

在上面的代码中,工作不在worker线程中循环,而是在开启工作的线程中循环,也就是说,它会不断地开启新的worker线程。这会导致的一个问题是,上一轮的回调还没执行完成,下一轮的工作就已经开始了。

那么为什么呢?下面来分析一下原因。

首先,要知道CyclicBarrier是如何做到在上一轮工作结束后下一轮工作开始前执行回调函数的。查看jdoc文档,里面有这么一句话“A CyclicBarrier supports an optional Runnable command that is run once per barrier point, after the last thread in the party arrives, but before any threads are released. ”这是描述回调函数的,从描述中可以看到,回调函数是在最后一个线程到达约定点后,线程释放前被执行的。也就是说,回调函数的执行时间发生在下一轮工作前,这是通过在执行完回调函数再释放工作线程来实现的。

然后,我们再来看看上面错误的使用方法。在错误的使用方法中,主线程的每一轮循环中都开启了新的worker线程,这样在回调函数结束之前,前面开启的worker线程确实没有得到释放,但是,新开启的工作线程却完全可以执行下一轮工作,这就是为什么在回调函数执行完毕之前,新一轮的工作就已经开始了的原因。并且,错误方法中的每一个工作线程只执行一轮工作就结束了,每一轮工作之间的线程互不影响,这也就失去了协作性,因此,千万要避免写出这种代码。

关于CyclicBarrier使用的最佳时间,基本上就是官方示例中的用法了,如下:

 class Solver {
final int N;
final float[][] data;
final CyclicBarrier barrier; class Worker implements Runnable {
int myRow;
Worker(int row) { myRow = row; }
public void run() {
while (!done()) {
processRow(myRow); try {
barrier.await();
} catch (InterruptedException ex) {
return;
} catch (BrokenBarrierException ex) {
return;
}
}
}
} public Solver(float[][] matrix) {
data = matrix;
N = matrix.length;
barrier = new CyclicBarrier(N,
new Runnable() {
public void run() {
mergeRows(...);
}
});
for (int i = 0; i < N; ++i)
new Thread(new Worker(i)).start(); waitUntilDone();
}
}

最后在有一个问题是,回调函数是在哪一个线程里执行的?

根据我的demo测试发现,是在第一个到达的线程中执行的。当然,官方并没有明确规定这一点,也许以后会有变化吧,所以,我们也不能以来这一特征。我的demo如下:

public class Demo1 {
  public static main(String[] args){
Demo1 demo = new Demo1();
demo1.showInfThreadWhenDirectly();
}
private void process(CyclicBarrier cyclicBarrier) {
final int n = 100;
Runnable worker= new Runnable() {
@Override
public void run() {
for (int i = 0; i < n; i++) {
try {
Thread.sleep(3000);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
try {
int arrival_index=cyclicBarrier.await();
if(0==arrival_index){
System.out.println("first arrival Thread in this iteration is: "
+Thread.currentThread().getId());
}
} catch (BrokenBarrierException | InterruptedException ex) {
ex.printStackTrace();
}
}
System.out.println("Worker is done");
System.out.println("Thread of Worker is "+ Thread.currentThread().getId());
}
}; Thread t1 = new Thread(worker);
Thread t2 = new Thread(worker);
t1.start();
t2.start();
} public void showInfThreadWhenDirectly(){
CyclicBarrier cyclicBarrier = new CyclicBarrier(2, () ->
System.out.println("[Directly] Thread in invert call function is"
+ Thread.currentThread().getId()));
process(cyclicBarrier);
System.out.println("[Directly] main Thread is "+ Thread.currentThread().getId());
} }

输出结果如下:

[Directly] main Thread is 1
[Directly] Thread in invert call function is10
first arrival Thread in this iteration is: 10
[Directly] Thread in invert call function is10
first arrival Thread in this iteration is: 10
[Directly] Thread in invert call function is10
first arrival Thread in this iteration is: 10
[Directly] Thread in invert call function is10
first arrival Thread in this iteration is: 10
[Directly] Thread in invert call function is11
first arrival Thread in this iteration is: 11
[Directly] Thread in invert call function is10
first arrival Thread in this iteration is: 10
[Directly] Thread in invert call function is10
first arrival Thread in this iteration is: 10
[Directly] Thread in invert call function is10
first arrival Thread in this iteration is: 10
[Directly] Thread in invert call function is11
first arrival Thread in this iteration is: 11

另外,官方还有一段:“

If the barrier action does not rely on the parties being suspended when it is executed, then any of the threads in the party could execute that action when it is released. To facilitate this, each invocation of await() returns the arrival index of that thread at the barrier. You can then choose which thread should execute the barrier action, for example:

  if (barrier.await() == 0) {
// log the completion of this iteration
}

意思是说,如果回调动作“arrier action”不需要在所有工作线程都停止的状态下执行的话,那么可以随便找一个工作线程去做这个动作。为了支持这个,CyclicBarrier 的await( )方法有一个返回值,返回的就是当前线程是第几个到达约定点(barrier)的。

参考https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/CyclicBarrier.html

CyclicBarrier正确的使用方法和错误的使用方法的更多相关文章

  1. Servlet常见错误及解决方法

    常见错误及解决方法 1. 404产生的原因为Web服务器(容器)根据请求地址找不到对应资源,以下情况都会出现404的错误提示: 输入的地址有误(应用名大小写不正确,名称拼写不正确) 在web.xml文 ...

  2. 关于启动Visual Studio 2010 旗舰版的几个错误的解决方法。

    关于启动Visual Studio 2010 旗舰版的几个错误的解决方法.亲测. 重做系统之后,今天是第一次打开Visual Studio 2010 旗舰版码代码,结果遇到几个弹出的对话框,现在与大家 ...

  3. http 500错误怎么解决方法

    出现500错误的原因是很多的,一般来说,如果程序出错,那么在浏览器内会返回给用户一个友好的错误提示,统一称之为服务器500错误. 解决的方法就是您必须在http中能够正确的获得错误信息,方法为:请打开 ...

  4. 微信jssdk常见错误及解决方法

    调用config 接口的时候传入参数 debug: true 可以开启debug模式,页面会alert出错误信息.以下为常见错误及解决方法: invalid url domain当前页面所在域名与使用 ...

  5. Web服务器(容器)请求常见的错误及其解决方法

    首先我们来看看容器如何找到service()方法?(1)当在浏览器中输入 http://localhost:8080/firstweb/sayHi 这个地址后,容器是如何找到 HelloServlet ...

  6. centos linux 系统日常管理4 scp,rsync,md5sum,sha1sum,strace ,find Rsync 常见错误及解决方法 第十七节课

    centos linux 系统日常管理4  scp,rsync,md5sum,sha1sum,strace ,find Rsync 常见错误及解决方法  第十七节课 rsync可以增量同步,scp不行 ...

  7. 编程中遇到的Python错误和解决方法汇总整理

    这篇文章主要介绍了自己编程中遇到的Python错误和解决方法汇总整理,本文收集整理了较多的案例,需要的朋友可以参考下   开个贴,用于记录平时经常碰到的Python的错误同时对导致错误的原因进行分析, ...

  8. VC6.0开发中一些链接错误的解决方法

    (1)error LNK2001: unresolved external symbol _main 编号:LNK2001 直译:未解决的外部符号:_main. 错误分析:缺少main函数.看看mai ...

  9. 常见的SQL错误和解决方法

    前言 今天你会看到每个人——从新手到专家——在使用SQL时犯的各种常见错误.你不能永远避免犯任何错误,但是熟悉广泛的错误将帮助你在尽可能短的时间内解决这些错误. 注:在我们的例子中我们使用的是Orac ...

随机推荐

  1. java多线程 基础demo

    join()   让主进程等待子进程全部执行完 例子如下:   package mocker; public class TestThread5 extends Thread {      priva ...

  2. sweetalert 快速显示两个提示, 第二个显示不出的问题

    今天在使用 sweetalert 做提示框的时候, 有个操作快速做了两次提示, 发现第二次显示不出: sweetAlert({}, function() { $.get('', function() ...

  3. 10.05 最初对Linux的了解,对Shell的认识

    linux的起源:UNIX,代码开源,可以得到源代码,然后自己编译,通过以后可以运行程序. 特点:免费的;兼容POSIX1.0标准;多用户,多任务系统;良好的用户界面;提供了图形界面;支持多种文件系统 ...

  4. 推荐两款国人开发的html前段框架

    1.http://www.h-ui.net/  H-ui前端框架官方网站 2.http://www.builive.com/  BUI是基于JQuery的富客户端UI框架

  5. Selenium框架切换-----Selenium快速入门(七)

    上一篇说了窗口的切换,本篇说说框架的切换. 切换框架:是指切换html中的iframe标签元素或者frame标签元素,注意,并不包括frameset 以下是常用的方法: 方法 说明 WebDriver ...

  6. 大咖分享 | 一文解锁首届云创大会干货——下篇(文末附演讲ppt文件免费下载)

    本文承接上一篇:大咖分享 | 一文解锁首届云创大会干货--上篇(文末附演讲ppt文件免费下载),第一届云创大会留下干货太多,这里追加下篇,同样,文末提供大咖们的干货分享,点击附件可免费下载.     ...

  7. static修饰的方法不能被重写可以被继承

    今天我们谈谈为什么抽象类中不能有静态的抽象方法以及static修饰的方法不能被重写可以被继承 1 static修饰的方法不能被重写可以被继承我们知道static修饰的方法为静态方法,可以直接使用类名. ...

  8. 洛谷P5292 [HNOI2019]校园旅行(二分图+最短路)

    题面 传送门 题解 如果暴力的话,我们可以把所有的二元组全都扔进一个队列里,然后每次往两边更新同色点,这样的话复杂度是\(O(m^2)\) 怎么优化呢? 对于一个同色联通块,如果它是一个二分图,我们只 ...

  9. USB-Redirector-Technician 永久破解版(USB设备映射软件)

    USB-Redirector-Technician 这个软件对于搞安卓刷机的人想必非常熟悉,淘宝破解版售价:38 一个的东西 除了远程刷机,用于映射一些小型设备是没问题的,只要网跟得上~ USB-Re ...

  10. 记一次升级Ubuntu内核

      uname -a 查看当前使用内核版本 升级指定内核  apt-get install linux-image-4.4.0-131-generic dpkg --get-selections | ...