深入理解AQS之CyclicBarrier


并发编程系列


字面意思回环栅栏,通过它可以实现让一组线程等待至某个状态(屏障点)之后再全部同时执行。叫做回环是因为当所有等待线程都被释放以后,CyclicBarrier可以被重用。

在这里插入图片描述

CyclicBarrier的使用

构造方法

// parties表示屏障拦截的线程数量,每个线程调用 await 方法告诉 CyclicBarrier 我已经到达了屏障,然后当前线程被阻塞。
 public CyclicBarrier(int parties)
 // 用于在线程到达屏障时,优先执行 barrierAction,方便处理更复杂的业务场景(该线程的执行时机是在到达屏障之后再执行)

重要方法

//屏障 指定数量的线程全部调用await()方法时,这些线程不再阻塞
// BrokenBarrierException 表示栅栏已经被破坏,破坏的原因可能是其中一个线程 await() 时被中断或者超时
public int await() throws InterruptedException, BrokenBarrierException
public int await(long timeout, TimeUnit unit) throws InterruptedException, BrokenBarrierException, TimeoutException

//循环  通过reset()方法可以进行重置
CyclicBarrier应用场景

CyclicBarrier 可以用于多线程计算数据,最后合并计算结果的场景。

import java.util.Set;
import java.util.concurrent.*;

public class CyclicBarrierTest2 {

    //保存每个学生的平均成绩
    private ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<String, Integer>();

    private ExecutorService threadPool = Executors.newFixedThreadPool(3);

    private CyclicBarrier cb = new CyclicBarrier(3, () -> {
        int result = 0;
        Set<String> set = map.keySet();
        for (String s : set) {
            result += map.get(s);
        }
        System.out.println("三人平均成绩为:" + (result / 3) + "分");
    });


    public void count() {
        for (int i = 0; i < 3; i++) {
            threadPool.execute(new Runnable() {

                @Override
                public void run() {
                    //获取学生平均成绩
                    int score = (int) (Math.random() * 40 + 60);
                    map.put(Thread.currentThread().getName(), score);
                    System.out.println(Thread.currentThread().getName()
                            + "同学的平均成绩为:" + score);
                    try {
                        //执行完运行await(),等待所有学生平均成绩都计算完毕
                        cb.await();
                    } catch (InterruptedException | BrokenBarrierException e) {
                        e.printStackTrace();
                    }
                }

            });
        }
    }

    public static void main(String[] args) {
        CyclicBarrierTest2 cb = new CyclicBarrierTest2();
        cb.count();
    }
}

执行结果如下

pool-1-thread-1同学的平均成绩为:96
pool-1-thread-2同学的平均成绩为:72
pool-1-thread-3同学的平均成绩为:61
三人平均成绩为:76

利用CyclicBarrier的计数器能够重置,屏障可以重复使用的特性,可以支持类似“人满发车”的场景

import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

public class CyclicBarrierTest3 {

    public static void main(String[] args) {

        AtomicInteger counter = new AtomicInteger();
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                5, 5, 1000, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(100),
                (r) -> new Thread(r, counter.addAndGet(1) + " 号 "),
                new ThreadPoolExecutor.AbortPolicy());

        CyclicBarrier cyclicBarrier = new CyclicBarrier(5,
                () -> System.out.println("裁判:比赛开始~~"));

        for (int i = 0; i < 10; i++) {
            threadPoolExecutor.submit(new Runner(cyclicBarrier));
        }

    }

    static class Runner extends Thread {
        private CyclicBarrier cyclicBarrier;

        public Runner(CyclicBarrier cyclicBarrier) {
            this.cyclicBarrier = cyclicBarrier;
        }

        @Override
        public void run() {
            try {
                int sleepMills = ThreadLocalRandom.current().nextInt(1000);
                Thread.sleep(sleepMills);
                System.out.println(Thread.currentThread().getName() + " 选手已就位, 准备共用时: " + sleepMills + "ms" + cyclicBarrier.getNumberWaiting());
                cyclicBarrier.await();

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

执行结果如下

5 号  选手已就位, 准备共用时: 537ms0
2 号  选手已就位, 准备共用时: 661ms1
4 号  选手已就位, 准备共用时: 717ms2
1 号  选手已就位, 准备共用时: 745ms3
3 号  选手已就位, 准备共用时: 800ms4
裁判:比赛开始~~
4 号  选手已就位, 准备共用时: 634ms0
1 号  选手已就位, 准备共用时: 685ms1
2 号  选手已就位, 准备共用时: 721ms2
5 号  选手已就位, 准备共用时: 841ms3
3 号  选手已就位, 准备共用时: 926ms4
裁判:比赛开始~~
CyclicBarrier源码分析

关注点:
1.一组线程在触发屏障之前互相等待,最后一个线程到达屏障后唤醒逻辑是如何实现的
2.删栏循环使用是如何实现的
3.条件队列到同步队列的转换实现逻辑

在这里插入图片描述
从上图不难看出,CyclicBarrier的功能主要是靠ReentrantLock来实现的。调用java.util.concurrent.CyclicBarrier#await()方法的时候,线程开始等待,直到parties数量的线程都到达。

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

此处dowait就是等待的逻辑。

/**
* Main barrier code, covering the various policies.
*/
private int dowait(boolean timed, long nanos)
   throws InterruptedException, BrokenBarrierException,
          TimeoutException {
   final ReentrantLock lock = this.lock;
   首先获取锁
   lock.lock();
   try {
       final Generation g = generation;

Generation在构造出来之后除非调用breakBarrier方法,否则都是false

       if (g.broken)
           throw new BrokenBarrierException();

       if (Thread.interrupted()) {
           breakBarrier();
           throw new InterruptedException();
       }

count随着调用dowait方法而减少 如果所有线程都到达 那么index就为0,此时就会进入到下一轮并调用nextGeneration方法。

       int index = --count;
       if (index == 0) {  // tripped
           boolean ranAction = false;
           try {
               final Runnable command = barrierCommand;
               if (command != null)
                   command.run();
               ranAction = true;
               nextGeneration();
               return 0;
           } finally {
               if (!ranAction)
                   breakBarrier();
           }
       }

如果还有线程没有到达,index不为0.则线程会进入到条件队列进行等待。(进入条件队列等待会释放锁资源)

       // loop until tripped, broken, interrupted, or timed out
       for (;;) {
           try {
               if (!timed)
                   trip.await();
               else if (nanos > 0L)
                   nanos = trip.awaitNanos(nanos);
           } catch (InterruptedException ie) {
               if (g == generation && ! g.broken) {
                   breakBarrier();
                   throw ie;
               } else {
                   // We're about to finish waiting even if we had not
                   // been interrupted, so this interrupt is deemed to
                   // "belong" to subsequent execution.
                   Thread.currentThread().interrupt();
               }
           }

           if (g.broken)
               throw new BrokenBarrierException();

           if (g != generation)
               return index;

           if (timed && nanos <= 0L) {
               breakBarrier();
               throw new TimeoutException();
           }
       }
   } finally {
       lock.unlock();
   }
}

当所有线程都到达之后,调用nextGeneration方法如下

/**
 * Updates state on barrier trip and wakes up everyone.
 * Called only while holding lock.
 */
private void nextGeneration() {
    // signal completion of last generation
    trip.signalAll();
    // set up next generation
    count = parties;
    generation = new Generation();
}

在这里可以看到除了重新设置count值和重新构造Generation之外,最重要的就是通过signalAll来唤醒条件队列中等待的线程。也就是在上面dowait方法中等待的线程。

从上面的分析不难看出,其实CyclicBarrier的主要逻辑都是在dowait方法当中,调用一次dowait方法,count值就减少1,只要还没有减少到0,就会进入到条件队列中等待,一旦减少到零的时候,就会唤醒条件队列中的所有线程。

更多细节参考下图

https://www.processon.com/view/link/6197b0aef346fb271b36a2bf

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

lang20150928

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

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

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

打赏作者

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

抵扣说明:

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

余额充值