多线程-java.util.concurrent-CountDownLatch与CyclicBarrier

CountDownLatch介绍:

      CountDownLatch是java.util.concurrent包中的一个类。它主要用来协调多个线程之间的同步,起到一个同步器的作用。举个例子,一个旅游团有10个人,参观景点。大家自由活动。等大家都从景点出口出来集合之后再一起坐车去吃饭。每个人参观景点的速度是不一样的,可以理解为每个人就是一个线程。大巴车就是CountDownLatch。等所有人都上车了之后才会启动出发。通过JDK7 API手册 中看到 CountDownLatch类的方法并不多。我们一般用到的是 await()和countDown()这两个方法。下面我们先看看怎么使用。

CountDownLatch简单应用:

     我们设计一个业务场景。你有个程序,启动时要先检查数据库、网络、缓存的健康状况。只有全部检查完毕之后才能继续往下进行。

     写一个抽象类作为基类,把通用的参数和方法写进去,当做模板(类似模板设计模式)

abstract class AbstractBaseCheck implements Runnable{

    protected CountDownLatch countDownLatch;

    public AbstractBaseCheck(CountDownLatch countDownLatch) {
        this.countDownLatch = countDownLatch;
    }

    abstract void check() throws Exception;

    @Override
    public void run() {
        try {
            check();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //计数器减1,这个一定要写在finally代码快中
            countDownLatch.countDown();
        }
    }
}

     写三个类,分别模拟数据库、网络和缓存的检查。继承AbstractBaseCheck

     检查数据库:

class MysqlDB extends AbstractBaseCheck{
    public MysqlDB(CountDownLatch countDownLatch) {
        super(countDownLatch);
    }

    @Override
    public void check() throws Exception{
        System.out.println("数据库检查中....");
        //模拟检查数据库的健康情况
        Thread.sleep(2000);
        System.out.println("数据检查结束.....Ok");

    }
}

     检查网络:

class NetWork extends AbstractBaseCheck{
    public NetWork(CountDownLatch countDownLatch) {
        super(countDownLatch);
    }

    @Override
    void check() throws Exception {
        System.out.println("网络监测中.....");
        Thread.sleep(5000);
        System.out.println("数据库检查结束....Ok");
    }
}

     检查缓存:

class Cache extends AbstractBaseCheck{
    public Cache(CountDownLatch countDownLatch) {
        super(countDownLatch);
    }

    @Override
    void check() throws Exception {
        System.out.println("缓存监测中......");
        Thread.sleep(500);
        System.out.println("缓存监测结束.....Ok");
    }
}

      main方法开始测试:

public class CountDownLatchTest {
    public static void main(String[] args) throws Exception{
        System.out.println("启动检查中......");
        CountDownLatch latch = new CountDownLatch(3);
        ExecutorService pool = Executors.newCachedThreadPool();
        pool.execute(new MysqlDB(latch));
        pool.execute(new NetWork(latch));
        pool.execute(new Cache(latch));

        latch.await();

        System.out.println("启动检查结束..... Ok!!!");
        Thread.sleep(3000);
        pool.shutdown();

    }
}

    我们看到结果,等三个检查方法都执行完成后,程序才会结束。测试成功!!!

    我们回头看一下代码。

    1、在创建 CountDownLatch对象的时候,指定了一个系数‘3’ 就是初始化等待线程数是3

    2、在run()方法中要调用conutDownLatch.countDown()方法。

    3、在主方法中,最后调用了countDownLatch.await()方法。

    如果我们把latch.await()方法改成 latch.await(10, TimeUnit.MILLISECONDS),那就不会等待所有子检查程序全部结束了。main()的主线程先回运行结束。

CyclicBarrier介绍:

      CyclicBarrier是什么?我们先看看JDK7 API手册中的介绍:CyclicBarrier是一种同步机制,它允许一组线程全部互相等待以达到一个公共屏障点。它在限定固定大小的线程程序中很管用,线程之间必须相互等待。屏障称为循环屏障,因为它可以在释放等待线程之后重新使用。CyclicBarrier支持可选的Runnable命令。该命令在障碍中的最后一个线程到达之后但在任何线程释放之前运行。该屏障在任何一方继续之前更新共享状态很有用。

     翻译的不是很好。其实CyclicBarrier主要的作用就是等待线程全部执行完成后,再继续执行。跟CountDownLatch是不是很像,区别就是,CyclicBarrier是可以循环执行的。而CountDownLatch只执行一遍。

     CyclicBarrier有两个构造器:

     CyclicBarrier(int parties)

     CyclicBarrier(int paries, Runnable barrierAction)  //这个barrierAction就是等其他线程执行完,释放之前运行的一个线程命令

    我们接下来看看简单实用

CyclicBarrier简单应用

     根据CyclicBarrier的介绍,我们知道一个是带 Runnable参数的,一个是不带Runnable参数的。我们先写个不带Runnable参数的例子:

     先写一个Wrok类,实现Runnable接口。这个方法最重要一个方法就是 cb.await();

class Work implements Runnable{

    private CyclicBarrier cb;

    public Work(CyclicBarrier cb) {
        this.cb = cb;
    }

    @Override
    public void run() {
        try {
            Thread.sleep((long)(Math.random() * 1000));
            System.out.println("线程:" + Thread.currentThread().getName() + "到达集合点1,当前已经有:" + cb.getNumberWaiting() + "到达!!");
            cb.await();
            Thread.sleep((long)(Math.random() * 1000));
            System.out.println("线程:" + Thread.currentThread().getName() + "到达集合点2,当前已经有:" + cb.getNumberWaiting() + "到达!!");
            cb.await();
            Thread.sleep((long)(Math.random() * 1000));
            System.out.println("线程:" + Thread.currentThread().getName() + "到达集合点3,当前已经有:" + cb.getNumberWaiting() + "到达!!");
            cb.await();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

    再写一个类来测试: CyclicBarrier的参数是3所以我们就启动3个线程。这个例子比较简单。

public class CyclicBarrierTest {

    public static void main(String[] args) {
        CyclicBarrier cb = new CyclicBarrier(3);

        new Thread(new Work(cb)).start();
        new Thread(new Work(cb)).start();
        new Thread(new Work(cb)).start();
    }

}

      上面介绍了一个简单的例子,我们看到 new CyclicBarrier(3) 的参数是'3',下面也开启了3个线程。思考一下,如果这个参数我们设置为'2'或者'4'会怎样呢?

      通过做实验发现:

          1、如果参数为'2'也就是parties的数量 < 线程的数量。那么当有两个线程到达cb.await()后就过去了,第三个线程再没有跟它凑数达到'2'的,所以就会卡住不往下执行了。

          2、如果参数为'4'也就是parties的数量 > 线程的数量。那么当3个线程不够,所以都会卡在cb.await(),不会往下执行了。

      思考问题2:为什么上面的例子写了3个cb.await()?就是想说明CyclicBarrier是可以重复执行的。而不像CountDownLatch只能执行一次。

  

      下面的例子是带Runnable参数的。找了个一个《Think in Java》书上的例子。一个赛马游戏。每匹马随机跑几步,跑完画一条线,继续跑。直到一匹马最终到达终点为止。划线的线程就是Runnable的入参:

      先写一个Horse类:

class Horse implements Runnable {

    private int speed;
    private int number;
    private CyclicBarrier cyclicBarrier;
    private static Random random = new Random(47);


    public Horse(CyclicBarrier cyclicBarrier, int number) {
        this.cyclicBarrier = cyclicBarrier;
        this.number = number;
    }

    @Override
    public void run() {
        try {
            while (!Thread.interrupted()) {
                synchronized (this) {
                    speed += random.nextInt(3);
                }
                cyclicBarrier.await();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public synchronized int getSpeed() {
        return speed;
    }

    public String runLine() {
        StringBuffer sb = new StringBuffer();
        for(int i = 0; i < getSpeed(); i++) {
            sb.append("*");
        }
        sb.append(number);
        return sb.toString();
    }

    @Override
    public String toString() {
        return "Horse{" +
                "speed=" + speed +
                ", number=" + number +
                ", cyclicBarrier=" + cyclicBarrier +
                '}';
    }
}

      再写一个Race就是划线的类:

class Race implements Runnable {
    private static final int FINISH_LINE = 75;

    private List<Horse> houses = new ArrayList<>();
    private int sleepTime;
    private ExecutorService exec;
    public Race(List<Horse> houses, int sleepTime, ExecutorService exec) {
        this.houses = houses;
        this.sleepTime = sleepTime;
        this.exec = exec;
    }

    @Override
    public void run() {
        StringBuffer sb = new StringBuffer();
        for(int i = 0; i < FINISH_LINE; i++) {
            sb.append("=");
        }
        System.out.println(sb.toString());

        for(Horse house : houses) {
            System.out.println(house.runLine());
        }

        for(Horse house : houses) {
            if(house.getSpeed() >= FINISH_LINE) {
                System.out.println(house + "won");
                exec.shutdownNow();
                return;
            }
        }

        try {
            TimeUnit.MILLISECONDS.sleep(sleepTime);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}

   写main方法测试:

public class CyclicBarrierTest2 {
    public static void main(String[] args) {
        int horseNum = 5;
        List<Horse> hList = new ArrayList<>();
        ExecutorService exec = Executors.newCachedThreadPool();
        CyclicBarrier cb = new CyclicBarrier(horseNum, new Race(hList, 300, exec));

        for(int i = 0; i < horseNum; i++) {
            Horse horse = new Horse(cb, i);
            hList.add(horse);
            exec.execute(horse);
        }
    }
}

CountDownLatch与CyclicBarrier的区别:

       CountDownLatch与CyclicBarrier最大的区别就是CountDownLatch执行一次就结束了,而CyclicBarrier可以多次执行。

参考:

  【1】《Think In Java》4th

  【2】《Java高并发程序设计》,葛一鸣 郭超

posted @ 2021-02-08 16:28  寻找风口的猪  阅读(110)  评论(0编辑  收藏  举报