Java并发实例之CyclicBarrier的使用
最近一直整并发这块东西,顺便写点java并发的例子,给大家做个分享,也强化下自己记忆,如果有什么错误或者不当的地方,欢迎大家斧正。
cyclicbarrier是一种多线程并发控制实用工具,和countdownlatch非常类似,它也可以实现线程间的计数等待,但是它的功能比countdownlatch更加复杂且强大。
cyclicbarrier的介绍
cyclicbarrier 的字面意思是可循环(cyclic)使用的屏障(barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。线程进入屏障通过cyclicbarrier的await()方法。
cyclicbarrier默认的构造方法是cyclicbarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用await方法告诉cyclicbarrier我已经到达了屏障,然后当前线程被阻塞。
cyclicbarrier还提供一个更高级的构造函数cyclicbarrier(int parties, runnable barrieraction),用于在线程到达屏障时,优先执行barrieraction这个runnable对象,方便处理更复杂的业务场景。
public cyclicbarrier(int parties) { this(parties, null); } public int getparties() { return parties; }
实现原理:在cyclicbarrier的内部定义了一个lock对象,每当一个线程调用cyclicbarrier的await方法时,将剩余拦截的线程数减1,然后判断剩余拦截数是否为0,如果不是,进入lock对象的条件队列等待。如果是,执行barrieraction对象的runnable方法,然后将锁的条件队列中的所有线程放入锁等待队列中,这些线程会依次的获取锁、释放锁,接着先从await方法返回,再从cyclicbarrier的await方法中返回。
cyclicbarrier主要用于一组线程之间的相互等待,而countdownlatch一般用于一组线程等待另一组些线程。实际上可以通过countdownlatch的countdown()和await()来实现cyclicbarrier的功能。即 countdownlatch中的countdown()+await() = cyclicbarrier中的await()。注意:在一个线程中先调用countdown(),然后调用await()。
构造函数cyclicbarrier可以理解为循环栅栏,这个计数器可以反复使用。比如,假设我们将计数器设置为10,那么凑齐第一批10个线程后,计数器就会归零,然后接着凑齐下一批10个线程,这就是它的内在含义。
lol和王者荣耀的玩家很多,许多人应该都有打大龙的经历,话说前期大家打算一起去偷大龙,由于前期大家都比较弱,需要五个人都齐了才能打大龙,这样程序该如何实现呢?本人很菜,开始我的代码是这么写的(哈哈大家不要纠结我的时间):
public class killdragon { /** * 模拟打野去打大龙 */ public static void dayeplaydragon(){ system.out.println("打野在去打大龙的路上,需要10s"); } /** * 模拟上单去打大龙 */ public static void shangdanplaydragon(){ system.out.println("上单在去打大龙的路上,需要10s"); } /** * 模拟中单去打大龙 */ public static void zhongdanplaydragon(){ system.out.println("中单在去打大龙的路上,需要10s"); } /** * 模拟adc和辅助去打大龙 */ public static void adcandfuzhuplaydragon(){ system.out.println("adc和辅助在去打大龙的路上,需要10s"); } /** * 模拟大家一起去打大龙 */ public static void killdragon() { system.out.println("打大龙..."); } public static void main(string[] args) { dayeplaydragon(); shangdanplaydragon(); zhongdanplaydragon(); adcandfuzhuplaydragon(); killdragon(); }
结果如下:
打野在去打大龙的路上,需要10s 上单在去打大龙的路上,需要10s 中单在去打大龙的路上,需要10s adc和辅助在去打大龙的路上,需要10s 打大龙...
这完了,大家在路上的时间就花了40s了,显然是错误的。要是都这么干,对方把你塔都要偷光了。不行得改进下,怎么改呢,多线程并发执行,如是我改成了下面这样的,用volatile关键字。
private static volatile int i = 4; public static void main(string[] args) { new thread(new runnable() { @override public void run() { long start = system.currenttimemillis(); while (i!=0){ } while (i==0) { killdragon(); i--; long t = system.currenttimemillis() - start; system.out.println("总共耗时:"+t+"毫秒"); } } }).start(); new thread(new runnable() { @override public void run() { dayeplaydragon(); try { thread.sleep(10000); i--; } catch (interruptedexception e) { e.printstacktrace(); } } }).start(); new thread(new runnable() { @override public void run() { shangdanplaydragon(); try { thread.sleep(10000); i--; } catch (interruptedexception e) { e.printstacktrace(); } } }).start(); new thread(new runnable() { @override public void run() { zhongdanplaydragon(); try { thread.sleep(10000); i--; } catch (interruptedexception e) { e.printstacktrace(); } } }).start(); new thread(new runnable() { @override public void run() { adcandfuzhuplaydragon(); try { thread.sleep(10000); i--; } catch (interruptedexception e) { e.printstacktrace(); } } }).start(); }
结果如下:
打野在去打大龙的路上,需要10s 上单在去打大龙的路上,需要10s 中单在去打大龙的路上,需要10s adc和辅助在去打大龙的路上,需要10s 打大龙... 总共耗时:10005毫秒
结果似乎还不错,但是处理起来实在是有点麻烦,需要 while (i!=0)一直在那循环着。这时候学到了用 cyclicbarrier来处理,代码如下:
public static void main(string[] args) { cyclicbarrier barrier = new cyclicbarrier(5); new thread(new runnable() { @override public void run() { long start = system.currenttimemillis(); try { barrier.await(); } catch (interruptedexception e) { e.printstacktrace(); } catch (brokenbarrierexception e) { e.printstacktrace(); } killdragon(); long t = system.currenttimemillis() - start; system.out.println("总共耗时:"+t+"毫秒"); } }).start(); new thread(new runnable() { @override public void run() { dayeplaydragon(); try { thread.sleep(10000); barrier.await(); } catch (exception e) { e.printstacktrace(); } } }).start(); new thread(new runnable() { @override public void run() { shangdanplaydragon(); try { thread.sleep(10000); barrier.await(); } catch (exception e) { e.printstacktrace(); } } }).start(); new thread(new runnable() { @override public void run() { zhongdanplaydragon(); try { thread.sleep(10000); barrier.await(); } catch (exception e) { e.printstacktrace(); } } }).start(); new thread(new runnable() { @override public void run() { adcandfuzhuplaydragon(); try { thread.sleep(10000); barrier.await(); } catch (exception e) { e.printstacktrace(); } } }).start(); }
大家都没到达之前都等待,结果如下:
打野在去打大龙的路上,需要10s 上单在去打大龙的路上,需要10s 中单在去打大龙的路上,需要10s adc和辅助在去打大龙的路上,需要10s 打大龙... 总共耗时:10002毫秒
cyclicbarrier相当于线程的计数器:
cyclicbarrier初始化时规定一个数目,然后计算调用了cyclicbarrier.await()进入等待的线程数。当线程数达到了这个数目时,所有进入等待状态的线程被唤醒并继续。
cyclicbarrier就象它名字的意思一样,可看成是个障碍, 所有的线程必须到齐后才能一起通过这个障碍。
cyclicbarrier初始时还可带一个runnable的参数, 此runnable任务在cyclicbarrier的数目达到后,所有其它线程被唤醒前被执行。
当然这样使用cyclicbarrier和使用countdownlatch是没什么区别的,正如前文所说的cyclicbarrier的功能更加的复杂且强大。给大家看一个《实战java高并发程序设计》一书上的一个例子。
比如:司令下达命令,要求10个士兵去一起完成一项任务。这时,就会要求10个士兵先集合报道,接着,一起雄赳赳气昂昂地去执行任务。当10个士兵都执行完了任务,那么司机就可以对外宣称,任务完成。相比countdownlatch,cyclicbarrier可以接受一个参数作为barrieraction。所谓的barrieraction就是当计数器一次计数完成后,系统会执行的动作。如下构造函数,其中,parties表示技术总数,也就是参与的线程总数。
public cyclicbarrier(int parties, runnable barrieraction)
下面示例演示了上述任务场景
public class cyclicbarrierdemo { public static class soldier implements runnable { private string soldier; private final cyclicbarrier cyclicbarrier; public soldier(cyclicbarrier cyclicbarrier, string soldier) { this.soldier = soldier; this.cyclicbarrier = cyclicbarrier; } @override public void run() { try { cyclicbarrier.await(); dowork(); cyclicbarrier.await(); } catch (interruptedexception e) { e.printstacktrace(); } catch (brokenbarrierexception e) { e.printstacktrace(); } } void dowork() { try { thread.sleep(math.abs(new random().nextint() % 10000)); } catch (interruptedexception e) { e.printstacktrace(); } system.out.println(soldier + ":任务完成"); } } public static class barrierrun implements runnable { boolean flag; int n; public barrierrun(boolean flag, int n) { this.flag = flag; this.n = n; } @override public void run() { if (flag) { system.out.println("司令:[士兵" + n + "个,任务完成!"); } else { system.out.println("司令:[士兵" + n + "个,集合完毕!"); flag = true; } } } public static void main(string args[]) { final int n = 10; thread[] allsoldier = new thread[n]; boolean flag = false; cyclicbarrier cyclicbarrier = new cyclicbarrier(n, new barrierrun(flag, n)); system.out.println("集合队伍!"); for (int i = 0; i < n; i++) { system.out.println("士兵" + i + "报道!"); allsoldier[i] = new thread(new soldier(cyclicbarrier, "士兵" + i)); allsoldier[i].start(); } } }
执行结果如下:
集合队伍! 士兵0报道! 士兵1报道! 士兵2报道! 士兵3报道! 士兵4报道! 士兵5报道! 士兵6报道! 士兵7报道! 士兵8报道! 士兵9报道! 司令:[士兵10个,集合完毕! 士兵0:任务完成 士兵2:任务完成 士兵9:任务完成 士兵3:任务完成 士兵7:任务完成 士兵8:任务完成 士兵1:任务完成 士兵4:任务完成 士兵5:任务完成 士兵6:任务完成 司令:[士兵10个,任务完成!
总结
以上就是本文关于java并发实例之cyclicbarrier的使用的全部内容,希望对大家有所帮助。感兴趣的朋友可以继续参阅本站:
java并发学习之blockingqueue实现生产者消费者详解
如有不足之处,欢迎留言指出。期待您的宝贵意见!