JUC---线程的并发工具类CountDownLatch 和CyclicBarrier

前言

今天我们要来唠的就是Java并发工具包下的CountDownLatch 和CyclicBarrier,他们两者的使用和异同。

1. CountDownLatch(扣除点)

1.CountDownLatch的作用:是一个线程等待其他工作线程完成工作以后才能再继续执行,相当于加强版的join。
(换生活中通俗点的例子,就是我们玩王者荣耀游戏的时候,必须10个玩家都进入游戏,游戏才能进入选人界面。我们玩家就相当于工作线程,而游戏选人界面就相当于主线程)
2.这JUC的类中是使用await() 进行阻塞等待的,而使用countDown() 负责计数器-1(即我们10个玩家都进入时,计数器减为0,便可进入游戏。)
3.一般使用场景:当我们主线程需要等待初始化线程完成后,才能进一步操作(主线程的执行是由外部线程决定的)

代码演示:
业务需求:我们有5个初始化线程,6个扣除点。只有等扣除完毕以后,主线程和业务线程才能继续自己的工作。

定义初始化线程
(作简单的打印工作,没初始化一个,扣减一次。)

 //初始化线程
 private static class InitThread implements Runnable{

     @Override
     public void run() {
         System.out.println("Thread_"+Thread.currentThread().getId()
                             +" ready init work...");
         latch.countDown();  //初始化线程完成工作了,
         for (int i = 0; i < 2; i++) {
             System.out.println("Thread_"+Thread.currentThread().getId()
                                 +"...........continue do its work");
         }
     }
 }

定义业务线程
(业务线程调用await() 方法进入阻塞状态等待,扣减点减为0才放行。)

//业务线程
 private static class BusiThread implements Runnable{

      @Override
      public void run() {

          try {
              latch.await(); //等待初始化线程完成工作
          } catch (InterruptedException e) {
              e.printStackTrace();
          }

          for(int i = 0; i < 3; i++){
              System.out.println("BusiThread_"+ Thread.currentThread().getId()
                                  +" do business");
          }
      }
  }

完整代码
(在main线程中new出4个初始化线程和一个单独的线程(主要是为了证明一个线程也可以扣减两次),并new出一个业务线程,让业务线程和主线程都通过await() 方法进行阻塞,直到扣除点减为0放行为止,观察控制台的输出)

package 并发工具类;

import java.util.concurrent.CountDownLatch;

/**
 * 类说明:演示CountDownLatch,有5个初始化线程,6个扣除点
 * 扣除完毕以后,主线程和业务线程才能继续自己的工作
 */
public class UseCountDownLatch {

    static CountDownLatch latch = new CountDownLatch(6);

    //初始化线程
    private static class InitThread implements Runnable{

        @Override
        public void run() {
            System.out.println("Thread_"+Thread.currentThread().getId()
                                +" ready init work...");
            latch.countDown();  //初始化线程完成工作了,
            for (int i = 0; i < 2; i++) {
                System.out.println("Thread_"+Thread.currentThread().getId()
                                    +"...........continue do its work");
            }
        }
    }

    //业务线程
    private static class BusiThread implements Runnable{

        @Override
        public void run() {

            try {
                latch.await(); //等待初始化线程完成工作
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            for(int i = 0; i < 3; i++){
                System.out.println("BusiThread_"+ Thread.currentThread().getId()
                                    +" do business");
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {

        //单独的初始化线程,初始化分为2步,需要扣减两次
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1);
                    System.out.println("Thread_"+Thread.currentThread().getId()
                                        +" ready init work step 1st.....");
                    latch.countDown();  //每完成一步初始化工作,扣减一次
                    System.out.println("begin 2nd.....");
                    Thread.sleep(1);
                    System.out.println("Thread_"+Thread.currentThread().getId()
                                        +" ready init work step 2nd.....");
                    latch.countDown();

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

        new Thread(new BusiThread()).start(); //启动业务线程
        for (int i = 0; i <= 3; i++) { //启动4个初始化线程
            Thread thread = new Thread(new InitThread());
            thread.start();

        }
        latch.await();
        System.out.println("Main do its work....");
    }

}

控制台输出(可以看到业务线程和主线程确实要等待初始化线程初始完毕,即6个扣除点减为0才会继续工作)
JUC---线程的并发工具类CountDownLatch 和CyclicBarrier_第1张图片

2. CyclicBarrier(栅栏)

1.一组线程运行时,有一定的先后顺序。先到达栅栏的线程会被阻塞,直到这一组线程都到达该屏障后,屏障才会开放,所有阻塞的线程才能继续运行。
(举我们生活中的例子,比如我们4个人一起参加一个团队游戏,每个人需要通过的关卡不一样。但我们都会拿到一把钥匙,并最终来到胜利之门,这个门需要4把钥匙都集其才会打开,我们才能一起获得比赛的胜利)
区别:使用CyclicBarrier, 我们这组线程向下执行的条件由我们自身决定。而使用CountDownLatch, 我们所能决定的是外部线程,只有我们这组线程把扣除点扣除完之后,外面的线程才能执行、
2. 相关的构造方法

 CyclicBarrier(int parties)
 CyclicBarrier(int parties, Runnable barrierAction) 
 //屏障开发,barrierAction定义的任务也会执行
 //应用场景:当有多个表单做处理,等这些表单处理的线程完成后,再开启屏障处理表单汇总工作。

3.代码示例
说明:我们构建出了5个栅栏,5个负者工作的线程(他们会因为一个随机数而进入休眠状态,这样5个线程到达屏障的时间可能不一样),并通过一个CollectThread来收集这5个工作线程的结果(由于我们使用的是携带线程的构造参数,它会随着栅栏开放而自动执行)

private static CyclicBarrier barrier = new CyclicBarrier(5,
            new CollectThread());

完整代码

package 并发工具类;

import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CyclicBarrier;


// CyclicBarrier的使用
public class UseCyclicBarrier {



    private static CyclicBarrier barrier = new CyclicBarrier(5,
            new CollectThread());

    private static ConcurrentHashMap<String, Long> resultMap
            = new ConcurrentHashMap<>();  //存放子线程工作结果的容器

    public static void main(String[] args) {
        for(int i = 0; i <=4; i++){
            new Thread(new SubThread()).start();
        }
    }

    //负者屏障开放以后的工作
    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);
            Random r = new Random(); //随机决定工作线程是否睡眠
            try{
                if(r.nextBoolean()){
                    Thread.sleep(2000 + id);
                    System.out.println("Thread_" + id
                    +"... do something ");
                }
                System.out.println(id + "....is await");
                //await()也是一个计数,等待全部完成之后就会放行
                barrier.await();

                Thread.sleep(1000+id);
                //这句打印等等5个线程一起到达屏障才会执行。
                System.out.println("Thread_" + id + "...do its business");
            }catch (Exception e){
                e.printStackTrace();
            }

        }
    }


}

控制台输出
JUC---线程的并发工具类CountDownLatch 和CyclicBarrier_第2张图片
CountDownLatch 和CyclicBarrier解析

  1. CountDownLatch 放行由第三者控制(即外部线程),CyclicBarrier放行由一组线程本身控制。
  2. CountDownLatch 放行的条件 >= 线程数; CyclicBarrier放行的条件 = 线程数

你可能感兴趣的:(JUC)