CyclicBarrier应用举例

1.定义

CyclicBarrier是juc下一个多线程锁,下面是jdk对它的定义

A synchronization aid that allows a set of threads to all wait for each other to reach a common barrier point. CyclicBarriers are useful in programs involving a fixed sized party of threads that must occasionally wait for each other. The barrier is called cyclic because it can be re-used after the waiting threads are released.

翻译过来就是

一种同步辅助工具,允许一组线程都等待彼此到达一个共同的屏障点。CyclicBarriers在涉及固定大小的线程组的程序中很有用,这些线程组必须偶尔相互等待。该屏障被称为循环屏障,因为它可以在等待线程释放后重新使用。

可以简单地理解为客满发车,再客满再发车,一个加法计数锁

2.关键api

  1. 构造函数,parties表示屏障值,barrierAction表示到达屏障值之后执行的Runnable
public CyclicBarrier(int parties, Runnable barrierAction)
  1. 函数是阻塞一个函数,每执行一次,则累加一个值,当达到屏障值则执行被阻塞的线程,如果凑不齐屏障值,则那些线程就会丢失
public int await() throws InterruptedException, BrokenBarrierException
public int await(long timeout, TimeUnit unit)
        throws InterruptedException,
               BrokenBarrierException,
               TimeoutException
  1. 查看还差多少个线程达到屏障点
public int getNumberWaiting()
  1. 重置屏障值,如果有线程未到达屏障值,这将触发BrokenBarrierException,从而结束这个线程
public void reset()

3. 简单应用

假设有这么一个场景

有一个物业经理,派出手下9个员工去收取物业费,每回来3个员工,就将收取的物业费打包存款

使用CyclicBarrier可以实现,总共有9个线程,每个线程返回一个数值,屏障值为3,达到屏障时求和
对应的代码如下

使用BlockingQueue阻塞队列用于存放收取的物业费
使用AtomicInteger 原子类用于计算收取的总额
getNumber用于模拟收取物业费的线程
BATCH_SIZE 为屏障值,到达屏障值之后调用calcSum
calcSum用于计算每一组已收取的物业费

private static final int THREAD_COUNT = 9;
private static final int BATCH_SIZE = 3;
private static final BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(BATCH_SIZE);
private static final AtomicInteger sum = new AtomicInteger(0);
private static CyclicBarrier barrier;

public static void main(String[] args) {
    
    
    ExecutorService executor = Executors.newFixedThreadPool(THREAD_COUNT);
    barrier = new CyclicBarrier(BATCH_SIZE, () -> {
    
    
        calcSum(BATCH_SIZE);
    });

    for (int i = 0; i < THREAD_COUNT; i++) {
    
    
        final int index = i;
        executor.execute(() -> {
    
    
            try {
    
    
                // 将数据放入队列
                queue.put(getNumber(index));
                barrier.await();
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
    
    
                System.out.println("触发BrokenBarrierException异常。");
            }
        });
    }

    try {
    
    
        executor.shutdown();
        if (executor.awaitTermination(10, TimeUnit.SECONDS)) {
    
    
            System.out.println("线程执行完毕");
        } else {
    
    
            System.out.println("线程超时");
        }

        executor.close();
    } catch (Exception ex) {
    
    
        ex.printStackTrace();
    }

    System.out.println("Total sum: " + sum.get());
}

private static void calcSum(int size) {
    
    
    List<Integer> batch = new ArrayList<>();
    for (int i = 0; i < size; i++) {
    
    
        try {
    
    
            batch.add(queue.take());
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
    }
    System.out.print("已收取: "+batch);
    int batchSum = batch.stream().mapToInt(Integer::intValue).sum();
    System.out.println("Batch sum: " + batchSum);
    sum.addAndGet(batchSum);
}

static Random random = new Random();

private static int getNumber(int index) {
    
    
    try {
    
    
        Thread.sleep(random.nextInt(100));
    } catch (InterruptedException ex) {
    
    
        ex.printStackTrace();
    }
    return index;
}

执行结果为,每一组的结果可能不一样,但是Total sum肯定是36

已收取: [0, 2, 1]Batch sum: 3
已收取: [7, 8, 4]Batch sum: 19
已收取: [6, 3, 5]Batch sum: 14
线程执行完毕
Total sum: 36

4.进阶应用

还是同一个例子,这次员工数不是屏障值的整数倍,比如说

有一个物业经理,派出手下11个员工去收取物业费,每回来3个员工,就将收取的物业费打包存款

修改

private static final int THREAD_COUNT = 10;

再次执行上面的代码,可以发现线程一直在等待中,直到线程超时,程序依然没有退出

已收取: [1, 0, 2]Batch sum: 3
已收取: [4, 5, 6]Batch sum: 15
已收取: [7, 3, 8]Batch sum: 18
线程超时

这是因为11%3=2, 最后两个到达的线程没有达到屏障值,因此CyclicBarrier一直在等待
因此需要提前结束CyclicBarrier
调用CyclicBarrier.reset()方法重置屏障,这将触发BrokenBarrierException异常,结束barrier.await();等待
定义一个倒计次数锁CountDownLatch,统计到达的员工,当每个员工都到达之后,获取等待数量,如果大于零,则重置屏障,计算剩余2个员工收到的费用,计算总数

代码如下

private static final int THREAD_COUNT = 11;
private static final int BATCH_SIZE = 3;
private static final BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(BATCH_SIZE);
private static final AtomicInteger sum = new AtomicInteger(0);
private static CyclicBarrier barrier;

public static void main(String[] args) {
    
    
    CountDownLatch countDownLatch = new CountDownLatch(THREAD_COUNT);
    ExecutorService executor = Executors.newFixedThreadPool(THREAD_COUNT);
    barrier = new CyclicBarrier(BATCH_SIZE, () -> {
    
    
        calcSum(BATCH_SIZE);
    });

    for (int i = 0; i < THREAD_COUNT; i++) {
    
    
        final int index = i;
        executor.execute(() -> {
    
    
            try {
    
    
                // 将数据放入队列
                queue.put(getNumber(index));
                countDownLatch.countDown();
                barrier.await();
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
    
    
                System.out.println("触发BrokenBarrierException异常。");
            }
        });
    }

    try {
    
    
        countDownLatch.await( );
        int numberWaiting = barrier.getNumberWaiting();
        if (numberWaiting != 0) {
    
    
            System.out.println("不是整数倍。都已执行完,重置CyclicBarrier。");
            barrier.reset();
            calcSum(numberWaiting);
        }

        executor.shutdown();
        if (executor.awaitTermination(10, TimeUnit.SECONDS)) {
    
    
            System.out.println("线程执行完毕");
        } else {
    
    
            System.out.println("线程超时");
        }

        executor.close();
    } catch (Exception ex) {
    
    
        ex.printStackTrace();
    }

    System.out.println("Total sum: " + sum.get());
}

private static void calcSum(int size) {
    
    
    List<Integer> batch = new ArrayList<>();
    for (int i = 0; i < size; i++) {
    
    
        try {
    
    
            batch.add(queue.take());
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
    }
    System.out.print("已收取: " + batch);
    int batchSum = batch.stream().mapToInt(Integer::intValue).sum();
    System.out.println("Batch sum: " + batchSum);
    sum.addAndGet(batchSum);
}

static Random random = new Random();

private static int getNumber(int index) {
    
    
    try {
    
    
        Thread.sleep(random.nextInt(1));
    } catch (InterruptedException ex) {
    
    
        ex.printStackTrace();
    }
    return index;
}

输出如下

已收取: [7, 4, 3]Batch sum: 14
已收取: [6, 0, 8]Batch sum: 14
已收取: [9, 10, 5]Batch sum: 24
不是整数倍。都已执行完,重置CyclicBarrier。
已收取: [2, 1]Batch sum: 3
触发BrokenBarrierException异常。
触发BrokenBarrierException异常。
线程执行完毕
Total sum: 55