CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活
这个屏障之所以用循环修饰,是因为在所有的线程释放彼此之后,这个屏障是可以重新使用的(reset()方法重置屏障点),这一点与CountDownLatch不同
CyclicBarrier是一种同步机制允许一组线程相互等待,等到所有线程都到达一个屏障点才退出await方法,它没有直接实现AQS而是借助ReentrantLock来实现的同步机制。它是可循环使用的,而CountDownLatch是一次性的,另外它体现的语义也跟CountDownLatch不同,CountDownLatch减少计数到达条件采用的是release方式,而CyclicBarrier走向屏障点(await)采用的是Acquire方式,Acquire是会阻塞的,这也实现了CyclicBarrier的另外一个特点,只要有一个线程中断那么屏障点就被打破,所有线程都将被唤醒(CyclicBarrier自己负责这部分实现,不是由AQS调度的),这样也避免了因为一个线程中断引起永远不能到达屏障点而导致其他线程一直等待。屏障点被打破的CyclicBarrier将不可再使用(会抛出BrokenBarrierException)除非执行reset操作
构造函数
CyclicBarrier有两个构造函数:
1 CyclicBarrier(int parties):int类型的参数表示有几个线程来参与这个屏障拦截,(拿上面的例子,即有几个人跟团旅游)
2 CyclicBarrier(int parties,Runnable barrierAction):当所有线程到达一个屏障点时,优先执行barrierAction这个线程
最重要的一个方法:await();每个线程调用await(),表示我已经到达屏障点,然后当前线程被阻塞
demo
//创建初始化3个线程的线程池 private ExecutorService threadPool = Executors.newFixedThreadPool(3); //创建3个CyclicBarrier对象,执行完后执行当前类的run方法 private CyclicBarrier cb = new CyclicBarrier(3, this); //保存每个学生的平均成绩 private ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>(); private void count() { for (int i = 0; i < 3; i++) { threadPool.execute(() -> { //计算每个学生的平均成绩,代码略()假设为60~100的随机数 int score = (int) (Math.random() * 40 + 60); try { Thread.sleep(Math.round(Math.random() * 1000)); } catch (InterruptedException e) { e.printStackTrace(); } map.put(Thread.currentThread().getName(), score); System.out.println(Thread.currentThread().getName() + "同学的平均成绩为" + score); try { //执行完运行await(),等待所有学生平均成绩都计算完毕 cb.await(); } catch (InterruptedException | BrokenBarrierException e) { e.printStackTrace(); } }); } threadPool.shutdown(); } @Override public void run() { int result = 0; Set<String> set = map.keySet(); for (String s : set) { result += map.get(s); } System.out.println("三人平均成绩为:" + (result / 3) + "分"); } public static void main(String[] args) throws InterruptedException { long now = System.currentTimeMillis(); CyclicBarrierTest cb = new CyclicBarrierTest(); cb.count(); Thread.sleep(100); long end = System.currentTimeMillis(); System.out.println("耗时(毫秒):" + (end - now)); }
显然没有阻塞主线程