CyclicBarrier源码解析与应用详解

概述

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还有几点需要注意的地方:

  1. CyclicBarrier的计数器可以重置而CountDownLatch不行,这意味着CyclicBarrier实例可以被重复使用而CountDownLatch只能被使用一次。而这也是循环屏障循环二字的语义所在。
  2. 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异常,然后继续向下。

结论:

  1. 内部有一个人把规则破坏了(接收到中断信号),其他人都不按规则来了,不会等待了
  2. 接收到中断信号的线程,await方法会触发InterruptedException异常,然后被唤醒向下运行
  3. 其他等待中或者后面到达的线程,会在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来实现的。

基本流程:

  1. 首先初始化一个ReentranLock对象,将parties赋值给变量count,初始化一个Condition,初始化一个Generation,Generation只有一个broken布尔值对应着当前的同步状态是否被破坏,将Runnable对象保存给barrierCommad。
  2. 当线程调用await()的时候,会先抢夺lock锁,然后将count--,如果count==0.说明线程都已经执行完毕了,可以开始执行barrierCommand了,如果有就执行,并且通过SingalAll()来唤醒所有等待的线程,并且将Generation重新初始化,将count重新赋值为parties,开始下一轮的同步。
  3. 如果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 不会阻塞主线程,只会阻塞子线程。
相关推荐
一 乐22 分钟前
基于vue船运物流管理系统设计与实现(源码+数据库+文档)
前端·javascript·数据库·vue.js·spring boot·后端·船运系统
jerry6091 小时前
注解(Annotation)
java·数据库·sql
Future_yzx1 小时前
Java Web的发展史与SpringMVC入门学习(SpringMVC框架入门案例)
java·前端·学习
沈韶珺1 小时前
Elixir语言的安全开发
开发语言·后端·golang
辞半夏丶北笙2 小时前
最近最少使用算法(LRU最近最少使用)缓存替换算法
java·算法·缓存
小菜鸟博士2 小时前
手撕Vision Transformer -- Day1 -- 基础原理
人工智能·深度学习·学习·算法·面试
星如雨グッ!(๑•̀ㅂ•́)و✧3 小时前
Java NIO全面详解
java·python·nio
码界筑梦坊3 小时前
基于Django的个人博客系统的设计与实现
后端·python·django·毕业设计
taopi20244 小时前
android java系统弹窗的基础模板
android·java·开发语言
松仔log4 小时前
Java多线程——对象的组合
java·开发语言·jvm