线程并发工具类:CyclicBarrier
-
概述:
-
工作表现:等到所有的线程都达到屏障后,大家再一起继续执行;先到的,调用这个工具类里面的await()方法
-
工作示意图:
-
应用场景:将初始化等工作中将初始化操作拆分,等到所有的初始化执行完成后,再执行主线程的业务逻辑
-
应用细节:在设置线程等待数的时候,一定要与实际的线程中相等
- 当设置的等待数小于实际的线程数,会让满足等待条件的线程执行下一个阶段,相当于是可以进行分组设置屏障的;而多出来的那个线程会一直等待,导致程序不能正常执行完毕
- 当设置的等待数大于实际的线程数,程序会一直阻塞,无法满足屏障越过条件
-
-
代码
-
源码分析:
-
构造函数一
//parties,代表屏障门票(需要有几个线程同时到了,才能往下走) public CyclicBarrier(int parties) { this(parties, null); }
-
构造函数二
//支持传入Runnable,当所有的线程都到达屏障后,先去执行这个barrierAction //再执行后面的:应用场景(达到屏障的线程各自承担了一部分计算,但是结果需要合并),那么这个合并工作中就可以用这个barrierAction public CyclicBarrier(int parties, Runnable barrierAction) { if (parties <= 0) throw new IllegalArgumentException(); this.parties = parties; this.count = parties; this.barrierCommand = barrierAction; }
-
-
具体实现:设置了4个做初始化任务的子线程,每个子线程做完各自的初始化任务后,调用await方法进行挂起;等到所有的子线程均执行完毕后,将其全部唤醒,执行相应的逻辑
-
代码:
package cn.enjoyedu.ch2.tools; import java.util.Map; import java.util.Random; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CyclicBarrier; /** *类说明:演示CyclicBarrier用法,共4个子线程,他们全部完成工作后,交出自己结果, *再被统一释放去做自己的事情,而交出的结果被另外的线程拿来拼接字符串 */ public class UseCyclicBarrier { //实例化,注意,这个参数不能传多了,传多了,实际就只有3个,你传个4进来,他就会一直等; private static CyclicBarrier cyclicBarrier = new CyclicBarrier(4); //存放所有相互协作的子线程的结果 private static ConcurrentHashMap<String,Long> resultMap = new ConcurrentHashMap<>();//存放子线程工作结果的容器 public static void main(String[] args) { for(int i=0;i<4;i++){ Thread thread = new Thread(new SubThread()); thread.start(); } System.out.println("主线程执行完成"); } //汇总的任务 private static class CollectThread implements Runnable{ @Override public void run() { StringBuilder result = new StringBuilder(); for(Map.Entry<String,Long> workResult:resultMap.entrySet()){ result.append("["+workResult.getValue()+"]"); } System.out.println(" the result = "+ result); System.out.println("do other business........"); } } //代表相互协调的子线程,初始化操作进行拆分 private static class SubThread implements Runnable{ @Override public void run() { long id = Thread.currentThread().getId(); resultMap.put(Thread.currentThread().getId()+"",id); try { Thread.sleep(1000+id); System.out.println("Thread_"+id+" ....do something "); //调用 cyclicBarrier.await():一旦调用await后,就代表这个线程已经到了屏障这里了,此时花旗 //等到所有线程都调用await之后,将其全部进行唤醒 // 此时这个线程需要等到相互协作的所有线程都到这个地方了,才一起继续执行 cyclicBarrier.await(); Thread.sleep(1000+id); System.out.println("Thread_"+id+" ....do its business "); } catch (Exception e) { e.printStackTrace(); } } } }
-
运行截图:主线线程与初始化线程可以并发执行,因为子线程的初始化任务相同(打印一条语句),可以观察到输出信息几乎同时出现
-
-
具体实现(CyclicBarrier高级用法):可以将初始化线程的执行结果汇总后再执行
-
代码:
package cn.enjoyedu.ch2.tools; import java.util.Map; import java.util.Random; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CyclicBarrier; /** *类说明:演示CyclicBarrier用法,共4个子线程,他们全部完成工作后,交出自己结果, *再被统一释放去做自己的事情,而交出的结果被另外的线程拿来拼接字符串 */ public class UseCyclicBarrier { //实例化,注意,这个参数不能传多了,传多了,实际就只有3个,你传个4进来,他就会一直等; private static CyclicBarrier cyclicBarrier = new CyclicBarrier(4,new CollectThread()); //存放所有相互协作的子线程的结果 private static ConcurrentHashMap<String,Long> resultMap = new ConcurrentHashMap<>();//存放子线程工作结果的容器 public static void main(String[] args) { for(int i=0;i<4;i++){ Thread thread = new Thread(new SubThread()); thread.start(); } System.out.println("主线程执行完成"); } //汇总的任务,将子线程执行的任务放到容器里面去,汇总线程就只需从容器里面去拿就行了 private static class CollectThread implements Runnable{ @Override public void run() { StringBuilder result = new StringBuilder(); for(Map.Entry<String,Long> workResult:resultMap.entrySet()){ result.append("["+workResult.getValue()+"]"); } System.out.println(" the result = "+ result); System.out.println("do other business........"); } } //代表相互协调的子线程,初始化操作进行拆分 private static class SubThread implements Runnable{ @Override public void run() { long id = Thread.currentThread().getId(); resultMap.put(Thread.currentThread().getId()+"",id); try { Thread.sleep(1000+id); System.out.println("Thread_"+id+" ....do something "); //调用 cyclicBarrier.await():一旦调用await后,就代表这个线程已经到了屏障这里了,此时花旗 //等到所有线程都调用await之后,将其全部进行唤醒 // 此时这个线程需要等到相互协作的所有线程都到这个地方了,才一起继续执行 cyclicBarrier.await(); Thread.sleep(1000+id); System.out.println("Thread_"+id+" ....do its business "); } catch (Exception e) { e.printStackTrace(); } } } }
-
运行截图:
-
-
-
CylicBarrier的可循环性
-
与CountDownLatch的不同点:
-
await方法是可以反复调用的,此时计数器会复位,会反复进行触发,让初始化任务线程多次在屏障中多次汇总,但是CountDownLatch中的计数器是无法复位的;复位+汇总--->可以实现多次汇总数据
-
在初始化线程的协调中:
- CountDownLatch:是由外部的线程进行协调的
- CylicBarrier:是由工作线程本身进行协调的
-
从构造函数触发,CountDownLatch中的构造实参(CNT)与执行初始化任务的子线程数时没有必然联系的(一个子线程中可以触发多次CNT递减),CylicBarrier的构造实参,是必须与初始化操作子线程数相同
-
CountDownLatch中工作线程不能多初始化线程的执行结果进行操作,但是CylicBarrier就是可以的,需要传递一个barrierAction
-
-
具体应用
- CountDownLatch用的比较多
- CylicBarrier主要用于需要处理子线程任务的汇总
-