一、介绍

CyclicBarrier也叫同步屏障,在JDK1.5被引入的一个同步辅助类,在API中是这么介绍的:

允许一组线程全部等待彼此达到共同屏障点的同步辅助。 循环阻塞在涉及固定大小的线程方的程序中很有用,这
些线程必须偶尔等待彼此。 屏障被称为循环,因为它可以在等待的线程被释放之后重新使用

CyclicBarrier好比一扇门,默认情况下关闭状态,堵住了线程执行的道路,直到所有线程都就位,门才打开,让所有线程一起通过。

二、实现分析

通过上图我们可以看到 CyclicBarrier的内部是使用重入锁ReentrantLock和Condition。它有两个构造方法:
 • CyclicBarrier(int parties) :它将在给定数量的参与者(线程)处于等待状态时启动,但它不会在启动屏障时执行预定义的操作。parties表示拦截线程的数量
 • CyclicBarrier(int parties, Runnable barrierAction) :创建一个新的 CyclicBarrier,它将在给定数量的参与者(线程)处于等待状态时启动,并在启动屏障时执行给定的屏障操作,该操作由最后一个进入屏障的线程执行

构造方法如下:

public CyclicBarrier(int parties, Runnable barrierAction) {
  if (parties <= 0) throw new IllegalArgumentException();
  this.parties = parties;
  this.count = parties;
  this.barrierCommand = barrierAction;
}
public CyclicBarrier(int parties) {
  this(parties, null);
}

在CyclicBarrier中最重要的方法莫过于await()方法,每个线程调用await方法告诉CyclicBarrier已经到达屏障位置,线程被阻塞。源码如下:

public int await() throws InterruptedException, BrokenBarrierException {
  try {
    return dowait(false, 0L);
 } catch (TimeoutException toe) {
    throw new Error(toe); // cannot happen
 }
}

await()方法的逻辑:如果该线程不是到达的最后一个线程,则他会一直处于等待状态,除非发生以下情况:

1. 最后一个线程到达,即index == 0
2 . 超出了指定时间(超时等待)
3. 其他的某个线程中断当前线程
4. 其他的某个线程中断另一个等待的线程
5. 其他的某个线程在等待屏障超时
6. 其他的某个线程在此屏障调用reset()方法。reset()方法用于将屏障重置为初始状态。

三、案例

案例1:

10个工程师一起来公司应聘,招聘方式分为笔试和面试。首先,要等人到齐后,开始笔试;笔试结束之后,再一起参加面试。把10个人看作10个线程,10个线程之间的同步过程如下图所示:

线程类MyThread1

import java.util.Random;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class MyThread1 extends Thread {

    private final CyclicBarrier barrier;
    private final Random random = new Random();

    public MyThread1(String name, CyclicBarrier barrier) {
        super(name);
        this.barrier = barrier;
    }

    @Override
    public void run() {
        try {
            System.out.println(Thread.currentThread().getName() + " - 向公司出发");
            Thread.sleep(random.nextInt(5000));
            System.out.println(Thread.currentThread().getName() + " - 已经到达公司");
            // 等待其他线程该阶段结束
            barrier.await();

            System.out.println(Thread.currentThread().getName() + " - 开始笔试");
            Thread.sleep(random.nextInt(5000));
            System.out.println(Thread.currentThread().getName() + " - 笔试结束");
            // 等待其他线程该阶段结束
            barrier.await();

            System.out.println(Thread.currentThread().getName() + " - 开始面试");
            Thread.sleep(random.nextInt(5000));
            System.out.println(Thread.currentThread().getName() + " - 面试结束");

        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }
    }
}

测试类

import java.util.concurrent.CyclicBarrier;

public class Main {
    public static void main(String[] args) {
//        CyclicBarrier barrier = new CyclicBarrier(5);
        CyclicBarrier barrier = new CyclicBarrier(5, new Runnable() {
            @Override
            public void run() {
                System.out.println("该阶段结束");
            }
        });

        for (int i = 0; i < 5; i++) {
            new MyThread1("线程-" + (i + 1), barrier).start();
        }

    }
}

打印如下:

线程-3 - 向公司出发
线程-1 - 向公司出发
线程-4 - 向公司出发
线程-2 - 向公司出发
线程-5 - 向公司出发
线程-3 - 已经到达公司
线程-2 - 已经到达公司
线程-1 - 已经到达公司
线程-4 - 已经到达公司
线程-5 - 已经到达公司
该阶段结束
线程-5 - 开始笔试
线程-3 - 开始笔试
线程-2 - 开始笔试
线程-4 - 开始笔试
线程-1 - 开始笔试
线程-2 - 笔试结束
线程-3 - 笔试结束
线程-1 - 笔试结束
线程-5 - 笔试结束
线程-4 - 笔试结束
该阶段结束
线程-4 - 开始面试
线程-2 - 开始面试
线程-3 - 开始面试
线程-1 - 开始面试
线程-5 - 开始面试
线程-4 - 面试结束
线程-1 - 面试结束
线程-2 - 面试结束
线程-5 - 面试结束
线程-3 - 面试结束

关于上面的方法,有几点说明:

1.CyclicBarrier是可以被重用的。以上一节的应聘场景为例,来了10个线程,这10个线程互相等待,到齐后一起被唤醒,各自执行接下来的逻辑;然后,这10个线程继续互相等待,到齐后再一起被唤醒。每一轮被称为一个Generation,就是一次同步点。

2.CyclicBarrier 会响应中断。10 个线程没有到齐,如果有线程收到了中断信号,所有阻塞的线程也会被唤醒,就是上面的breakBarrier()方法。然后count被重置为初始值(parties),重新开始。

3.上面的回调方法,barrierAction只会被第10个线程执行1次(在唤醒其他9个线程之前),即最后一个线程执行一次,而不是10个线程每个都执行1次。

案例2:

田径比赛,所有运动员准备好了之后,大家一起跑,代码如下

public class Demo1CyclicBarrier {
    public static void main(String[] args) {
        // 拦截线程的数量为5
        CyclicBarrier cyclicBarrier = new CyclicBarrier(5);
        List<Thread> threadList = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            Thread t = new Thread(new Athlete(cyclicBarrier, "运动员" + i));
            threadList.add(t);
        }
        for (Thread t : threadList) { // 通过遍历逐一启动线程
            t.start();
        }
    }
    static class Athlete implements Runnable {
        private CyclicBarrier cyclicBarrier;
        private String name;
        public Athlete(CyclicBarrier cyclicBarrier, String name) {
                this.cyclicBarrier = cyclicBarrier;
                this.name = name;
        }
        @Override
        public void run() {
            System.out.println(name + "就位");
            try {
                // 每个线程调用await方法告诉CyclicBarrier已经到达屏障位置,线程被阻塞
                // 如果该线程不是到达的最后一个线程,则他会一直处于等待状态
                cyclicBarrier.await();
                System.out.println(name + "跑到终点。");
            } catch (Exception e) {
            }
        }
    }
}

结果如下:

运动员0就位
运动员1就位
运动员2就位
运动员3就位
运动员4就位
运动员4跑到终点。
运动员3跑到终点。
运动员2跑到终点。
运动员1跑到终点。
运动员0跑到终点。

即运动员全部就位之后才会开始跑步。

 

posted on 2023-01-16 09:12  周文豪  阅读(217)  评论(0编辑  收藏  举报