java.util.concurrent.CyclicBarrier也是JDK 1.5提供的一个同步辅助类(为什么用也呢?参见再谈CountDownLatch),它允许一组线程互相等待,直到到达某个临界点(a common barrier point,翻译成公共障碍点、公共栅栏点都不够传神,直接用临界点吧)。在某个程序中,一组固定大小的线程必须互相等待时,CyclicBarrier将起很大的作用。因为在等待线程被释放后,这个临界点可以重用,所以说是循环的。
CyclicBarrier支持一个可选的Runnable,在一组线程中的最后一个线程完成之后、释放所有线程之前,该Runnable在屏障点运行一次(每循环一次Runnable运行一次)。这种方式可以用来在下一波继续运行的线程运行之前更新共享状态(比如下一波僵尸来之前,检查武器弹药)。
CountDownLatch是不能够重复使用的,是一次性的,其锁定一经打开,就不能够在重复使用。就像引线,点燃后就在燃烧减少,燃烧完了就不能再次使用了。CyclicBarrier是一种循环的方式进行锁定,这次锁定被打开之后,还能够重复计数,再次使用。就像沙漏,这次漏完了,倒过来接着漏。
还有一点是两者之间很大的区别,就是CountDownLatch在等待子线程的过程中,会锁定主线程,而CyclicBarrier不会锁定主线程,只是在所有子线程结束后,根据定义执行其可选的Runnable线程。
所以在这两种辅助类中进行选择时,能够很明显进行区分。
可以考虑这么一种情况,我们需要向数据库导入一些数据,没导入几条希望能进行一次计时,便于我们查看。因为实现比较简单,直接上代码:
package howe.demo.thread.cyclicbarrier; import java.util.Random; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.TimeUnit; /** * @author liuxinghao * @version 1.0 Created on 2014年9月17日 */ public class CyclicBarrierTest { public static void main(String[] args) throws InterruptedException { final long start = System.currentTimeMillis(); final CyclicBarrier barrier = new CyclicBarrier(3, new Runnable() { @Override public void run() { long end = System.currentTimeMillis(); System.out.println("导入" + 3 + "条数据,至此总共用时:" + (end - start) + "毫秒"); } }); for (int i = 0; i < 9; i++) { final int threadID = i + 1; new Thread(new Runnable() { @Override public void run() { try { TimeUnit.SECONDS.sleep(new Random().nextInt(10));// 模拟业务操作 System.out.println(threadID + "完成导入操作。"); barrier.await(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } } }).start(); } System.out.println("====主线程结束===="); } }
执行结果为:
====主线程结束==== 4完成导入操作。 2完成导入操作。 1完成导入操作。 导入3条数据,至此总共用时:4006毫秒 5完成导入操作。 6完成导入操作。 8完成导入操作。 导入3条数据,至此总共用时:4007毫秒 3完成导入操作。 0完成导入操作。 7完成导入操作。 导入3条数据,至此总共用时:8006毫秒
程序没导入3条会进行一次计时,统计已经执行的时间。如果CyclicBarrier构造函数的数字和for循环的次数相等的话,这个就是总共用时。
考虑一下上面的例子,如果for循环的次数不是CyclicBarrier监听次数的整数倍,比如是10,那执行结果将会是:
====主线程结束==== 2完成导入操作。 5完成导入操作。 4完成导入操作。 导入3条数据,至此总共用时:4005毫秒 8完成导入操作。 1完成导入操作。 3完成导入操作。 导入3条数据,至此总共用时:5005毫秒 7完成导入操作。 6完成导入操作。 0完成导入操作。 导入3条数据,至此总共用时:8005毫秒 9完成导入操作。
在打印完“9完成导入操作。”之后,将一直等待。在这里可以通过barrier.getNumberWaiting()查看还差多少个线程达到屏障点。如果出现这种情况,那就需要和CountDownLatch配合使用了,当子线程全部执行完,有判断barrier.getNumberWaiting()不等于0,则调用barrier.reset()重置。这个时候将会触发BrokenBarrierException异常,但是将结束整个过程。
修改的代码如下:
package howe.demo.thread.cyclicbarrier; import java.util.Random; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.TimeUnit; /** * @author liuxinghao * @version 1.0 Created on 2014年9月17日 */ public class CyclicBarrierTest { public static void main(String[] args) throws InterruptedException { final long start = System.currentTimeMillis(); final CountDownLatch count = new CountDownLatch(10); final CyclicBarrier barrier = new CyclicBarrier(3, new Runnable() { @Override public void run() { long end = System.currentTimeMillis(); System.out.println("导入" + 3 + "条数据,至此总共用时:" + (end - start) + "毫秒"); } }); for (int i = 0; i < 10; i++) { final int threadID = i + 1; new Thread(new Runnable() { @Override public void run() { try { TimeUnit.SECONDS.sleep(new Random().nextInt(10));// 模拟业务操作 System.out.println(threadID + "完成导入操作。"); count.countDown(); barrier.await(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { System.out.println("触发BrokenBarrierException异常。"); } } }).start(); } count.await(); if(barrier.getNumberWaiting() != 0) { System.out.println("不是整数倍。都已执行完,重置CyclicBarrier。"); barrier.reset(); } System.out.println("====主线程结束===="); } }
执行结果为:
3完成导入操作。 9完成导入操作。 6完成导入操作。 导入3条数据,至此总共用时:3005毫秒 8完成导入操作。 5完成导入操作。 10完成导入操作。 导入3条数据,至此总共用时:7005毫秒 1完成导入操作。 7完成导入操作。 4完成导入操作。 2完成导入操作。 导入3条数据,至此总共用时:9005毫秒 不是整数倍。都已执行完,重置CyclicBarrier。 ====主线程结束==== 触发BrokenBarrierException异常。
使用barrier.reset()进行重置,因为CyclicBarrier是一个循环,开头就是结尾,所以重置也可以理解为直接完成。
另外,因为使用了CountDownLatch,所以主线程会锁定,直到线程通过count.await()向下执行。