了解循环屏障CyclicBarrier

CyclicBarrier是常用的同步辅助工具类,它的作用是让一组线程达到屏障点时被阻塞,直到指定数量的线程都达到屏障点,才取消阻塞,此时被阻塞的线程才能继续执行。循环屏障可以重复使用。

SyclicBarrier构造方法有两个CyclicBarrier(int parties)和CyclicBarrier(int parties, Runnable barrierAction)。第一个参数parties指定可以打开屏障的线程数量,第二个参数barrierAction指定一旦打开屏障就会执行(非立即)的操作。

只有调用await()方法的线程才会达到屏障点进行等待,当等待的线程数量达到循环屏障设置的值parties,屏障才会打开,并执行打开屏障的操作。而getNumberWaiting()方法返回当前阻塞的线程数量。

如下模拟一个循环屏障反复使用的实例:5个学生相约一起赶校车,一起上学,一起点名,一起学习。

public class TestCyclicBarrier {

    public static void main(String[] args) throws InterruptedException {
        System.out.println("太阳公公出门啦");
        CyclicBarrier cyclicBarrier = new CyclicBarrier(5, new BarrierPoint(0));
        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread(new Student(i + "号学生", cyclicBarrier));
            thread.start();
        }
    }

    static class BarrierPoint implements Runnable {
        private int id;

        public BarrierPoint(int id) {
            this.id = id;
        }

        @Override
        public void run() {
            if (id == 0) {
                System.out.println("发车啦");
                id = 1;
                return;
            }
            if (id == 1) {
                System.out.println("开始点名");
                id = 2;
                return;
            }
            if (id == 2) {
                System.out.println("准备上课");
                return;
            }
        }
    }

    static class Student implements Runnable {
        private String name;
        private CyclicBarrier cyclicBarrier;

        public Student(String name, CyclicBarrier cyclicBarrier) {
            this.name = name;
            this.cyclicBarrier = cyclicBarrier;
        }

        @Override
        public void run() {
            try {
                Thread.sleep((long) (Math.random() * 2000 + 1000));
                System.out.println(name + "开始上车");
                cyclicBarrier.await();
                Thread.sleep((long) (Math.random() * 2000 + 1000));
                System.out.println(name + "进入教室");
                cyclicBarrier.await();
                Thread.sleep((long) (Math.random() * 2000 + 1000));
                System.out.println(name + "翻开课本");
                cyclicBarrier.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        }
    }
}

控制台输出的执行结果如下:

太阳公公出门啦
1号学生开始上车
3号学生开始上车
0号学生开始上车
4号学生开始上车
2号学生开始上车
发车啦
0号学生进入教室
1号学生进入教室
4号学生进入教室
2号学生进入教室
3号学生进入教室
开始点名
1号学生翻开课本
4号学生翻开课本
0号学生翻开课本
3号学生翻开课本
2号学生翻开课本
准备上课

在这个案例中,由于await()方法调用了3次,所以5条线程都被阻塞了3次。第一次发生阻塞是由于校车需要等待这5个学生都上车了才可以发车,第二次阻塞是由于5个学生都需要到达教室才能开始点名,第三次阻塞是由于5个学生都准备好课本老师才开始讲课。

本例中通过BarrierPoint来维护每次打开屏障需要执行的操作,由于每次打开屏障所执行的操作都可能不一样,所以通过id来标识具体的操作内容。

如果调用await()方法的线程数量大于循环屏障设置的值,被阻塞的线程将永远阻塞。且屏障打开后的操作线程不是立即执行的。

以下是一个模拟永远阻塞的实例:

public class TestCyclicBarrier2 {

    public static void main(String[] args) {
        CyclicBarrier cb = new CyclicBarrier(3, new Runnable() {
            @Override
            public void run() {
                System.out.println("屏障打开");
            }
        });
        for (int i = 0; i < 10; i++) {
            Thread worker = new Thread(new Worker(cb));
            worker.start();
        }
    }

    static class Worker implements Runnable {
        private CyclicBarrier cb;

        public Worker(CyclicBarrier cb) {
            super();
            this.cb = cb;
        }

        @Override
        public void run() {
            try {
                System.out.println(Thread.currentThread().getName() + "到达屏障");
                cb.await();
                Thread.sleep(2000);
                System.out.println("屏障等待的线程数量:" + cb.getNumberWaiting());
            } catch (InterruptedException | BrokenBarrierException e) {
                e.printStackTrace();
            }
        }
    }
}

控制台输入如下:

Thread-0到达屏障
Thread-1到达屏障
Thread-3到达屏障
Thread-2到达屏障
Thread-4到达屏障
屏障打开
Thread-5到达屏障
屏障打开
Thread-6到达屏障
Thread-9到达屏障
Thread-7到达屏障
屏障打开
Thread-8到达屏障
屏障等待的线程数量:1
屏障等待的线程数量:1
屏障等待的线程数量:1
屏障等待的线程数量:1
屏障等待的线程数量:1
屏障等待的线程数量:1
屏障等待的线程数量:1
屏障等待的线程数量:1
屏障等待的线程数量:1

可以看出控制台输出这些内容后没有停止而是一直运行,因为有一个线程阻塞了,那就是Thread-8。我们创建了10条线程,但是循环屏障只能同时让3条线程互相等待,所以有9条线程执行完毕后,必然有剩余1条线程到达屏障时被阻塞,此处Thread-8就是那条很不幸被永久阻塞的线程。

另外从输出内容看,输出【屏障打开】的语句间歇性每隔3个线程就执行,这是为什么呢?

之前有提到过 屏障打开后的操作线程不是立即执行的。本例中 【Thread-0到达屏障】,【Thread-1到达屏障】,【Thread-3到达屏障】后其实循环屏障已经打开了,但是【Thread-2到达屏障】和【Thread-4到达屏障】的线程抢先执行,所以【屏障打开】这个执行线程慢了一拍就导致看到图中的输出效果。同理可以分析后面输出错乱的原因。但无论如何Thread-8是被永久阻塞的线程,这个结论肯定是没错的,因为先到达屏障的线程先释放。

另外 CountDownLatch和CyclicBarrier 是比较相似的,但有明显的不同点:

CountDownLatch必须通过countDown和await方法协同工作控制阻塞时机,且CountDownLatch是一次性的,不能被复位。

CyclicBarrier只需调用await方法就能控制阻塞时机,且CyclicBarrier的await方法是可以重复使用的,每次屏障打开后可以自动关闭,以实现下次继续阻塞的效果。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Alphathur

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值