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