package com.example.util.concurrent;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * CountDownLatch类是一个同步计数器，构造时传入int参数，该参数就是计数器的初始值，
 * <p>
 * 每调用一次countDown()方法，计数器减1，计数器大于0时，await()方法会阻塞程序继续执行。
 * <p>
 * CountDownLatch如其所写，是一个倒计数的锁存器，当计数减至0时触发特定的事件。
 * <p>
 * 利用这种特性，可以让主线程等待子线程的结束。下面以一个模拟田径运动员跑步比赛的例子加以说明。
 */
public class CountDownLatchPlayers {

    private static final int PLAYER_AMOUNT = 5;

    /**
     * ExecutorService与生命周期
     * <p>
     * ExecutorService扩展了Executor并添加了一些生命周期管理的方法。一个Executor的生命周期有三种状态，运行，关闭，终止。
     * <p>
     * Executor创建时处于运行状态。
     * <p>
     * 当调用ExecutorService.shutdown()后，处于关闭状态，isShutdown()方法返回true。
     * <p>
     * 这时，不应该再想Executor中添加任务，所有已添加的任务执行完毕后，Executor处于终止状态，isTerminated()返回true。
     * <p>
     * 如果Executor处于关闭状态，往Executor提交任务会抛出unchecked exception RejectedExecutionException。
     *
     * @throws InterruptedException
     */
    public static void main(String[] args) {
        // 对于每位运动员，CountDownLatch减1后即结束比赛
        CountDownLatch begin = new CountDownLatch(1);
        // 对于整个比赛，所有运动员结束后才算结束
        CountDownLatch end = new CountDownLatch(PLAYER_AMOUNT);
        Player[] plays = new Player[PLAYER_AMOUNT];
        for (int i = 0; i < PLAYER_AMOUNT; i++) {
            plays[i] = new Player(i + 1, begin, end);
        }
        // 设置特定的线程池，大小为5
        ExecutorService executor = Executors.newFixedThreadPool(PLAYER_AMOUNT);
        for (Player p : plays) {
            // 分配线程
            executor.execute(p);
        }
        System.out.println("Race begins!");
        begin.countDown(); // 比赛开始，所有之前begin.await()的运动员在自己的线程里等待随机的时间完成之后，到达终点时end.countDown()
        try {
            end.await(); // 当前线程等待在这里，直到所有运动员全部到终点之后，即为比赛结束，再触发继续运行。
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println("Race ends!");
        }
        executor.shutdown();
    }
}

class Player implements Runnable {

    private int id;
    private CountDownLatch begin;
    private CountDownLatch end;

    public Player(int id, CountDownLatch begin, CountDownLatch end) {
        this.id = id;
        this.begin = begin;
        this.end = end;
    }

    @Override
    public void run() {
        try {
            begin.await(); // 所有运动员准备在起跑线上，等待begin.countDown()指示开始比赛
            Thread.sleep((long) (Math.random() * 1000)); // 随机分配时间，即运动员完成时间
            System.out.println("Play" + id + " arrived.");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            end.countDown(); // 当前运动员到达终点，使end状态减1，最终减至0
        }
    }
}