24.CountDownLatch-倒计时协调器

CountDownLatch-倒计时协调器

一、概述

1). CountDownLatch的作用

  1. Thread.join()可以让一个线程等待另一个线程执行结束再执行。有时候需要一个线程执行到某个阶段时另一个线程再执行而不必等到执行结束,java.util.concurrent.CountDownLatch可以实现此功能。

  2. CountDownLatch可以让一个或者多个线程等待其他线程完成一组特定操作(先决操作)之后再运行。

2). CountDownLatch的原理

  1. CountDownLatch内部会维护一个用于表示未完成的先决操作的数量。
  2. CountDownLatch.await()相当于一个受保护方法,其保护条件为 计数器值为0(代表所有先决操作已执行完毕),目标操作是一个空操作 (逻辑上不需要操作,只要线程满足条件不等待即可)
  3. CountDownLatch.countDown()每被调用执行一次,相应实例的计数器值减少1。
    • 计数器值为0时继续调用不会产生异常,此时调用也无意义。
    • 后续使用该CountDownLatch.await()的线程也不会被暂停。
    • 为保证程序的正确性,该方法必须放在总是能被执行的位置,比如finally语句块中。
  4. 当计数器值不为0时,CountDownLatch.await()的所有执行线程会被暂停(等待线程)。
  5. CountDownLatch.countDown()相当于一个通知方法,它会在计数器值达到0的时候唤醒相应实例上的所有等待线程。

一个CountDownLatch实例只能够实现一次等待和唤醒

CountDownLatch内部封装了对“全部先决操作已执行完毕”(计数器值为0)这个保护条件的等待与通知的逻辑,客户端代码在使用CountDownLatch实现等待/通知的时候调用await、countDown方法都无须加锁。

3). CountDownLatch的创建

// 签名指定计数器初始值
CountDownLatch downLatch = new CountDownLatch(5);

二、CountDownLatch案例

1). 案例一描述

某定制Web服务器在其启动时需要启动若干启动过程比较耗时的服务,为了尽可能地减少服务器启动过程的总耗时,该服务器会使用专门的工作者线程以并发的方式去启动这些服务。但是,服务器在所有启动操作结束后,需要判断这些服务的状态以检查服务器的启动是否是成功的。只有在这些服务全部启动成功的情况下该服务器才被认为是启动成功的,否则服务器的启动算失败,此时服务器会自动终止(退出Java虚拟机)。

2). 案例一简单实现

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CountDownLatch;

/**
 * @author Liucheng
 * @since 2019-12-06
 */
public class ServiceManager {

    public static void main(String[] args) {
        List services = new ArrayList<>();
        CountDownLatch latch = new CountDownLatch(3);

        Service service1 = new Service(latch);
        Service service2 = new Service(latch);
        Service service3 = new Service(latch);

        services.add(service1);
        services.add(service2);
        services.add(service3);

        for (Service service : services) {
            service.start();
        }

        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        boolean status = true;

        for (Service service : services) {
            status = service.getStatus();
            if (!status) {
                break;
            }
        }

        if (status) {
            System.out.println("系统启动成功");
        } else {
            System.out.println("系统启动失败");
        }
    }
}

class Service extends Thread{

    private static Random random = new Random();

    private final CountDownLatch latch;
    
    // CountDownLatch.countDown()本身是无法区分被报告操作的结果是成功的还是失败的,
    // 需要一个状态变量标识。
    private boolean status = false;

    public Service(CountDownLatch latch) {
        this.latch = latch;
    }

    @Override
    public void run() {

        // 随机睡眠,模拟线程启动
        try {
            int number = random.nextInt(1000);
            Thread.sleep(number);
            // 模拟随机启动失败
            if (number < 300) { throw new RuntimeException(); }
            this.status = true;
        } catch (Exception e) {
            System.out.println("启动失败");
            status = false;
        } finally {
            // 在finally中进行countDown操作;防止线程异常导致无法进行通知
            latch.countDown();
        }
    }

    public boolean getStatus() {
        return this.status;
    }
}

3). 避免CountDownLatch内部计数器由于程序的错误而永远无法达到0的措施

如果CountDownLatch内部计数器由于程序的错误而永远无法达到0,那么相应实例上的等待线程会一直处于WAITING状态。避免该问题的出现有两种方法。

  1. 确保所有CountDownLatch.countDown()调用都位于代码中正确的位置, 比如finally语句块中
  2. 等待线程在等待先决操作完成的时候指定一个时间限制,如下
    1. 返回true, 超时时间前被唤醒,所有CountDownLatch.countDown()执行完毕。
    2. 返回false, 由于超时而被唤醒,部分CountDownLatch.countDown()没有执行。
public boolean await(long timeout,TimeUnit unit) throws InterruptedException

4). 线程安全相关

  1. 对于同一个CountDownLatch实例latchlatch.countDown()的执行线程在执行该方法之前所执行的任何内存操作对等待线程在latch.await()调用返回之后的代码是可见的且有序的。
/**
 * @author Liucheng
 * @since 2019-12-08
 */
public class CountManyTimes {

    private static final CountDownLatch latch = new CountDownLatch(4);
    private static Random random = new Random();
    private static int data;

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

        Thread workedThread = new Thread(() -> {
            for (int i = 1; i < 10; i++) {
                data = i;
                latch.countDown();
                // 使当前线程随机暂停一段时间
                try {
                    Thread.sleep(random.nextInt(1000));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        workedThread.start();

        latch.await();
        System.out.printf("It`s done, data=%d", data);
    }
}

上述结果永远为4,不可能小于4.

你可能感兴趣的:(24.CountDownLatch-倒计时协调器)