线程并发工具类(CylicBarrier)

81 阅读4分钟

线程并发工具类:CyclicBarrier

  • 概述:

    • 工作表现:等到所有的线程都达到屏障后,大家再一起继续执行;先到的,调用这个工具类里面的await()方法

    • 工作示意图:

      image-20220222213305851

    • 应用场景:将初始化等工作中将初始化操作拆分,等到所有的初始化执行完成后,再执行主线程的业务逻辑

    • 应用细节:在设置线程等待数的时候,一定要与实际的线程中相等

      • 当设置的等待数小于实际的线程数,会让满足等待条件的线程执行下一个阶段,相当于是可以进行分组设置屏障的;而多出来的那个线程会一直等待,导致程序不能正常执行完毕
      • 当设置的等待数大于实际的线程数,程序会一直阻塞,无法满足屏障越过条件
  • 代码

    • 源码分析:

      • 构造函数一

         //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();
                     }
         ​
                 }
             }
         }
        
      • 运行截图:主线线程与初始化线程可以并发执行,因为子线程的初始化任务相同(打印一条语句),可以观察到输出信息几乎同时出现

        image-20220222214907333

    • 具体实现(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();
                     }
         ​
                 }
             }
         }
        
      • 运行截图:

        B

  • CylicBarrier的可循环性

    • 与CountDownLatch的不同点:

      1. await方法是可以反复调用的,此时计数器会复位,会反复进行触发,让初始化任务线程多次在屏障中多次汇总,但是CountDownLatch中的计数器是无法复位的;复位+汇总--->可以实现多次汇总数据

      2. 在初始化线程的协调中:

        • CountDownLatch:是由外部的线程进行协调的
        • CylicBarrier:是由工作线程本身进行协调的
      3. 从构造函数触发,CountDownLatch中的构造实参(CNT)与执行初始化任务的子线程数时没有必然联系的(一个子线程中可以触发多次CNT递减),CylicBarrier的构造实参,是必须与初始化操作子线程数相同

      4. CountDownLatch中工作线程不能多初始化线程的执行结果进行操作,但是CylicBarrier就是可以的,需要传递一个barrierAction

    • 具体应用

      • CountDownLatch用的比较多
      • CylicBarrier主要用于需要处理子线程任务的汇总