CountDownLatch是一次性的,CyclicBarrier正好可以循环使用。它允许一组线程互相等待,直到到达某个公共屏障点(common barrier point)。所谓屏障点就是一组任务执行完毕的时刻。下面我们来看一个实例:
package juc.latch; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; /** * 测试CyclicBarrier * @author donald * 2017年3月6日 * 下午12:52:10 */ public class TestCyclicBarrier { final CyclicBarrier barrier;//屏障锁 final int MAX_TASK;//屏障锁可共享数 /** * * @param cnt */ public TestCyclicBarrier(int cnt) { //主线程也需要锁一次,所以cnt + 1,主线程等待一组10个线程执行完, //同时主线程释放锁,达到屏障点,执行下一组线程 barrier = new CyclicBarrier(cnt + 1); //每一次,可运行的任务锁 MAX_TASK = cnt; } public void doWork(final Runnable work) { new Thread() { public void run() { work.run(); try { //释放屏障共享锁 int index = barrier.await(); //检查进度 doWithIndex(index); } catch (InterruptedException e) { return; } catch (BrokenBarrierException e) { return; } } }.start(); } /** * 检查线程组完成进度,根据不通过的进度,可以做一些通知工作 * @param index */ private void doWithIndex(int index) { if (index == MAX_TASK / 2) { System.out.println("Left 50%"); } if (index == 0) { System.out.println("run over"); } } public void waitForNext() { try { //主线程释放屏障共享锁,检查进度 doWithIndex(barrier.await()); } catch (InterruptedException e) { return; } catch (BrokenBarrierException e) { return; } } public static void main(String[] args) { final int count = 10; TestCyclicBarrier demo = new TestCyclicBarrier(count); for (int i = 0; i < 100; i++) { demo.doWork(new Runnable() { public void run() { // do something try { Thread.sleep(1000L); } catch (Exception e) { return; } } }); if ((i + 1) % count == 0) { /* 每10个线程一组,或者锁一个屏障点,当每组的10个线程,都完成时, 才执行下一组线程 */ demo.waitForNext(); } } } }
上述例子描述的是一个周期性处理任务的例子,在这个例子中有一对的任务(100个),希望每10个为一组进行处理,当前仅当上一组任务处理完成后才能进行下一组,另外在每一组任务中,当任务剩下50%,所有任务执行完成时向观察者发出通知。
在这个例子中,CyclicBarrierDemo 构建了一个count+1的任务组(其中一个任务时为了外界方便挂起主线程)。每一个子任务里,任务本身执行完毕后都需要等待同组内其它任务执行完成后才能继续。同时在剩下任务50%、30%已经0时执行特殊的其他任务(发通知)。很显然CyclicBarrier有以下几个特点:
•await()方法将挂起线程,直到同组的其它线程执行完毕才能继续
•await()方法返回线程执行完毕的索引,注意,索引时从任务数-1开始的,也就是第一个执行完成的任务索引为parties-1,最后一个为0,这个parties为总任务数,清单中是cnt+1
•CyclicBarrier 是可循环的,显然名称说明了这点。在清单1中,每一组任务执行完毕就能够执行下一组任务。
另外除了CyclicBarrier除了以上特点外,还有以下几个特点:
•如果屏障操作不依赖于挂起的线程,那么任何线程都可以执行屏障操作。在清单1中可以看到并没有指定那个线程执行50%、30%、0%的操作,而是一组线程(cnt+1)个中任何一个线程只要到达了屏障点都可以执行相应的操作
•CyclicBarrier 的构造函数允许携带一个任务,这个任务将在0%屏障点执行,它将在await()==0后执行。
•CyclicBarrier 如果在await时因为中断、失败、超时等原因提前离开了屏障点,那么任务组中的其他任务将立即被中断,以InterruptedException异常离开线程。
•所有await()之前的操作都将在屏障点之前运行,也就是CyclicBarrier 的内存一致性效果
CyclicBarrier 的所有API如下:
•public CyclicBarrier(int parties) 创建一个新的CyclicBarrier,它将在给定数量的参与者(线程)处于等待状态时启动,但它不会在启动barrier 时执行预定义的操作。
•public CyclicBarrier(int parties, Runnable barrierAction) 创建一个新的CyclicBarrier,它将在给定数量的参与者(线程)处于等待状态时启动,并在启动barrier 时执行给定的屏障操作,该操作由最后一个进入barrier 的线程执行。
•public int await() throws InterruptedException, BrokenBarrierException 在所有参与者都已经在此barrier 上调用await 方法之前,将一直等待。
•public int await(long timeout,TimeUnit unit) throws InterruptedException, BrokenBarrierException,TimeoutException 在所有参与者都已经在此屏障上调用await 方法之前将一直等待,或者超出了指定的等待时间。
•public int getNumberWaiting() 返回当前在屏障处等待的参与者数目。此方法主要用于调试和断言。
•public int getParties() 返回要求启动此barrier 的参与者数目。
•public boolean isBroken() 查询此屏障是否处于损坏状态。
•public void reset() 将屏障重置为其初始状态。
符: