栅栏CyclicBarrier
栅栏作用类似闭锁,不同点在于闭锁是一次性用品,栅栏可重复使用。另外闭锁的await方法是用来阻塞的,栅栏的await方法则类似闭锁的countDown方法,是用来做减法的,当栅栏初始化的个数减为零后,栅栏便执行它在初始化时指定的Runnable.run方法。
下面举例说明,使用线程池来调度多线程:
package com.wlf.concurrent; import java.util.Random; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class BarrierHarness { public void timeTasks(int num) { final long start = System.nanoTime(); System.out.println("开始计时..."); CyclicBarrier barrier = new CyclicBarrier(num, new Runnable() { // 栅栏动作,在计数器为0的时候执行 @Override public void run() { long end = System.nanoTime(); System.out.printf("战斗结束,耗时: %d 毫秒", (end - start) / 1000000); } }); // 使用线程池调度并发线程 ExecutorService es = Executors.newCachedThreadPool(); try { for (int i = 0; i < num; i++) { es.execute(new Soldier(i, barrier)); } } finally { es.shutdown(); } } /** * 具体执行命令的线程 * * @author Administrator * */ class Soldier implements Runnable { int num; CyclicBarrier barrier; public Soldier(int num, CyclicBarrier barrier) { this.num = num; this.barrier = barrier; } @Override public void run() { System.out.printf("我是士兵 %2d 号,现在进入战斗线程中,获取战斗任务编号:%d\n", num, new Random().nextInt()); // 栅栏计数器减1 try { barrier.await(); } catch (InterruptedException e) { return; } catch (BrokenBarrierException e) { return; } } } public static void main(String[] args) { new BarrierHarness().timeTasks(7); } }
运行结果:
开始计时... 我是士兵 0 号,现在进入战斗线程中,获取战斗任务编号:-961128239 我是士兵 6 号,现在进入战斗线程中,获取战斗任务编号:529020377 我是士兵 4 号,现在进入战斗线程中,获取战斗任务编号:-1723358857 我是士兵 2 号,现在进入战斗线程中,获取战斗任务编号:-671078051 我是士兵 5 号,现在进入战斗线程中,获取战斗任务编号:-1096701822 我是士兵 3 号,现在进入战斗线程中,获取战斗任务编号:1475521415 我是士兵 1 号,现在进入战斗线程中,获取战斗任务编号:1634562126 战斗结束,耗时: 62 毫秒
再看一个例子,这里反复使用同一个栅栏来实现同一时刻的并发执行和数据返回,其中用到的BoundedBuffer类参见信号量Semaphore使用实例
package com.wlf.concurrent; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicInteger; public class PutTakeTest { private static final ExecutorService pool = Executors.newCachedThreadPool(); // 计算所有生产者线程的随机数总和 private final AtomicInteger putSum = new AtomicInteger(0); // 计算所有消费者线程的随机数总和 private final AtomicInteger takeSum = new AtomicInteger(0); private final CyclicBarrier barrier; private final BoundedBuffer<Integer> bb; private final int nTrials, nPairs; public static void main(String[] args) { new PutTakeTest(10, 10, 100000).test(); pool.shutdown(); } PutTakeTest(int capacity, int npairs, int ntrials) { this.bb = new BoundedBuffer<Integer>(capacity); this.nTrials = ntrials; // 每个线程累加次数 this.nPairs = npairs; // 生产/消费的线程数 // 栅栏需要拦截的线程数量是:生产者+消费者+主线程 this.barrier = new CyclicBarrier(npairs * 2 + 1); } void test() { try { for (int i = 0; i < nPairs; i++) { pool.execute(new Producer()); // 提交生产者任务 pool.execute(new Consumer()); // 提交消费者任务 } barrier.await(); // 等待所有线程就绪,生产和消费任务都已提交后再同时开始并发执行生产和消费 barrier.await(); // 等待所有线程执行完成后,再同时获取生产和消费的统计结果 System.out.printf("putSum: %d, takeSum: %d\n", putSum.get(), takeSum.get()); } catch (Exception e) { throw new RuntimeException(e); } } /** * 生产者 * * @author Administrator * */ class Producer implements Runnable { public void run() { try { // 获取随机数种子 int seed = (this.hashCode() ^ (int) System.nanoTime()); int sum = 0; barrier.await(); // 再一次使用栅栏让生产者到齐后才执行下面逻辑 for (int i = nTrials; i > 0; --i) { bb.put(seed); // 放入有界队列 sum += seed; // 求和 seed = xorShift(seed); // 获取下一个随机数 } putSum.getAndAdd(sum); barrier.await(); // 再一次使用栅栏让生产者执行完毕后再返回结果 } catch (Exception e) { throw new RuntimeException(e); } } } class Consumer implements Runnable { public void run() { try { barrier.await(); int sum = 0; for (int i = nTrials; i > 0; --i) { sum += bb.take(); } takeSum.getAndAdd(sum); barrier.await(); } catch (Exception e) { throw new RuntimeException(e); } } } private static int xorShift(int y) { y ^= (y << 6); y ^= (y >>> 21); y ^= (y << 7); return y; } }
运行结果是随机的:
putSum: -471766499, takeSum: -471766499