一、介绍
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跑到终点。
即运动员全部就位之后才会开始跑步。