java并发工具类学习同步屏障CyclicBarrier

CyclicBarrier这个工具类要做的事情是让一组线程到达同一个屏障时被阻塞,直到最后一个线程到达屏障时屏障才会开门,所有被屏障拦截的线程才会继续运行。

测试代码如下:

import java.util.concurrent.CyclicBarrier;

/**
 * @Description
 * @Author DJZ-WWS
 * @Date 2019/5/27 15:36
 */
public class cyclicBarrierTest {

static CyclicBarrier  cyclicBarrier=new CyclicBarrier(2);

    public static void main(String[] args) {

        new  Thread(new Runnable() {
            @Override
            public void run() {
           try {
               cyclicBarrier.await();
           }catch (Exception e){

           }
                System.out.println(1);
            }
        }).start();
        try {
            cyclicBarrier.await();
        }catch (Exception e){

        }
        System.out.println(2);
    }

}

结果可能有两种

1

2

或者

2

1

因为主线程和子线程的调度是由CPU决定的,两个线程都有可能先执行,所以会产生两种结果。

如果把new CycleBarrier(2)改成new  Cyclebarrier(3),则主线程和子线程会永远等待,因为没有第三个线程执行await方法,即没有第三个线程到达屏障,所以之前到达屏障的两个线程都不会继续执行。

为了更清楚的说明工具类的作用把代码做了如下调整:

import java.util.concurrent.CyclicBarrier;

/**
 * @Description
 * @Author DJZ-WWS
 * @Date 2019/5/27 15:36
 */
public class cyclicBarrierTest {

static CyclicBarrier  cyclicBarrier=new CyclicBarrier(3);

    public static void main(String[] args) {

        new  Thread(() -> {
       try {
           System.out.println("1将要等待");
           cyclicBarrier.await();

           System.out.println("1睡醒了");
       }catch (Exception e){

       }
            System.out.println(1);
        }).start();

        new  Thread(() -> {
            try {
                System.out.println("2将要等待");
                cyclicBarrier.await();

                System.out.println("2睡醒了");
            }catch (Exception e){

            }
            System.out.println(2);
        }).start();



        try {
            System.out.println("main将要等待");
            cyclicBarrier.await();
        }catch (Exception e){

        }
        System.out.println(3);
    }

}

结果如下:

三个线程在调用await方法之前都是做自己的事情一旦调用了await方法那么就会等待其他线程将await方法之前的任务完成,等到所有的线程await前任务都完成了,那么他们开始执行自己各自的任务。

      CycleBarrier提供一个更高级的构造函数CycleBarrier(int parties,Runnable barrierAction)

用于在线程到达屏障时,优先执行barrierAction,方便处理更复杂的业务场景。

import java.util.concurrent.CyclicBarrier;

/**
 * @Description
 * @Author DJZ-WWS
 * @Date 2019/5/27 15:36
 */
public class cyclicBarrierTest {

    static CyclicBarrier cyclicBarrier = new CyclicBarrier(2,new  A());

    public static void main(String[] args) {

        new Thread(() -> {
            try {
                System.out.println("1将要等待");
                cyclicBarrier.await();

                System.out.println("1睡醒了");
            } catch (Exception e) {

            }
            System.out.println(1);
        }).start();



        try {
            System.out.println("main将要等待");
            cyclicBarrier.await();
        } catch (Exception e) {

        }
        System.out.println(3);
    }

    static class A implements Runnable {
        @Override
        public void run() {
            System.out.println("2");
        }
    }
}


执行结果如下:

他们都会进入等待状态,到达等待状态以后开始执行自己的任务时主线程在A和第一个线程都执行完毕之后才会执行主线程。

CycleBarrier的应用:

  CycleBarier 可以用于多线程计算数据,最后合并计算结果的场景。
 如用一个Excel保存了用户所有银行流水,每个sheet保存一个账户近一年的每笔银行流水 ,现在需要统计用户的日均银行流水,先用多线程处理每个sheet里的银行流水,现在需要统计用户的日均银行流水,最后,再用barrierAction用这些线程的计算结果,计算出整个Exel的日均银行流水。代码实现如下:

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

/**
 * @Description
 * 
 * CycleBarier 可以用于多线程计算数据,最后合并计算结果的场景。
 * 如用一个Excel保存了用户所有银行流水,每个sheet保存一个账户近一年的每笔银行流水
 * ,现在需要统计用户的日均银行流水,先用多线程处理每个sheet里的银行流水,
 * 现在需要统计用户的日均银行流水,最后,再用barrierAction用这些线程的计算结果,
 * 计算出整个Exel的日均银行流水
 * @Author DJZ-WWS
 * @Date 2019/5/27 16:48
 */
public class BankWaterService  implements   Runnable {

    /**
     * 创建4个屏障,处理完之后执行当前类的run方法
     */
    private CyclicBarrier  c=new CyclicBarrier(4,this);

    /**
     * 假设只有4个sheet,所以只启动4线程
     */
    private Executor executor=Executors.newFixedThreadPool(4);


    /**
     * 保存每个sheet的计算结果
     */


    private ConcurrentHashMap<String,Integer> sheetBankWaterCountMap=new ConcurrentHashMap<>();
    private   void  count(){
        for (int i = 0; i <4 ; i++) {
            executor.execute(() -> {
                //计算当前sheet的银行流水数据,计算代码省略
                sheetBankWaterCountMap.put(Thread.currentThread().getName(),1);
                //银行流水计算完成,插入一个屏障
               try {
                   c.await();
                   System.out.println("执行其他任务");
               }catch (Exception e){
                   e.printStackTrace();
               }
            });
        }
    }
    @Override
    public void run() {
        int  result =0;
        for (Map.Entry<String,Integer> sheet:sheetBankWaterCountMap.entrySet()){
            result+=sheet.getValue();
        }
        //将结果输出
        sheetBankWaterCountMap.put("result",result);
        System.out.println(result);
    }

    public static void main(String[] args) {
        /**
         * 每个线程都去执行自己的计算任务,待每个线程计算完毕,主线程开始计算汇总。然后再去执行await后面的其他任务
         */
        BankWaterService bankWaterService=new BankWaterService();
        bankWaterService.count();
    }
}

执行结果:

CountDownLatch和CycleBarrier的区别

            CountDownLatch计数器只能使用一次,Cyclebarrier(0可以使用reset的方法重置。CycleBarrier能处理更复杂业务场景。

比如:如果计算错误,可以重置计算器,并让线程重新执行一次。

            CycleBarrier提供了其他有用的方法,比如getNumWaiting方法可以获得Cyclebarrier阻塞的线程数量。isBroken()方法用来了解阻塞队列的线程是否被中断。

参考:《java 并发编程的艺术》

猜你喜欢

转载自blog.csdn.net/qq_35410620/article/details/90605107