多线程环境下CountDownLatch的用法

1、概述

谷歌直译:倒数计时
还有一些其他翻译:计数减小门闩,倒计时闩锁

CountDownLatch类所在的包路径: java.util.concurrent.CountDownLatch

一种同步辅助类,它允许一个或多个线程等待,直到在其他线程中执行的一组操作完成为止。

使用场景:在主线程中创建多个子线程,等待所有子线程执行完成之后,再切换到主线程等待位置并往下继续执行。

2、关键方法、函数

名称 描述
await() 导致当前线程等待,直到锁存器递减至零为止,除非该线程被中断。
boolean await(long timeout, TimeUnit unit) 导致当前线程等待,直到锁存器计数到零为止,除非该线程被中断或经过了指定的等待时间。
countDown() 减少锁存器的计数,如果计数达到零,则释放所有等待线程。
long getCount() 返回当前计数。返回值为0时,此后所有等待线程被释放,主线程继续执行await()后的代码。

3、案例

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

/**
 * Created by Administrator on 2020/4/24.
 */
public class TestCountDownLatch {
    private static ExecutorService es = Executors.newCachedThreadPool();
    static boolean isRun = false;

    public static void main(String[] args) {

        startTask();

        //如果线程池不再使用,可通过下面这句关闭线程池,释放资源。
        //es.shutdown();
    }

    private static volatile CountDownLatch latchInstance;

    private static CountDownLatch getInstance(final int count) {
        if (null == latchInstance) {
            synchronized (CountDownLatch.class) {
                if (null == latchInstance) {
                    latchInstance = new CountDownLatch(count);
                }
            }
        }
        return latchInstance;
    }

    /**
     * 启动轮询任务
     */
    private static void startTask() {
        int threadNum = 5;
        latchInstance = getInstance(threadNum);
        for (; ; ) {
            //TODO 3、关键语句  为0,则说明所有的子线程执行完成,会执行.await();之后的语句
            if (latchInstance.getCount() == 0) {
                //latchInstance只能用一次,用完之后变为0 ;为0之后就无法再进行倒计数了,如果要继续倒计数需要重新实例化
                latchInstance = null;
                //重新实例化时可以设置新的线程数;因为有可能在分组请求场景时,第一组可能只需要5个线程,到了第二轮则只需要3个线程就足够了,节省资源开销。
                latchInstance = getInstance(threadNum);
            }
            if (!isRun) {
                isRun = true;
                System.out.println("主线程开始执行…… ……");
                //在主线程中循环重建threadNum个子线程
                for (int i = 0; i < threadNum; i++) {
                    //往线程池扔子线程
                    es.execute(() -> {
                        try {
                            Thread.sleep(1000);
                            System.out.println("子线程:" + Thread.currentThread().getName() + "执行");
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        //TODO 1、关键语句  倒计数子线程执行完成情况
                        latchInstance.countDown();
                    });
                }
                try {
                    //TODO 2、关键语句 这句之后的代码(主线程)会等待所有子线程执行完成后才会继续往下执行。
                    latchInstance.await();
                    isRun = false;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    System.out.println(threadNum + "个子线程都执行完毕,继续往下执行主线程 " + isRun);
                    //第一次,所有子线程执行完成之后,假设:为了节省资源,我将线程数减少到3个。这个地方可以做很多文章,threadNum 是一个动态值,可动态控制子线程数量
                    threadNum = 3;
                }
            }
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
案例中关键的几个地方:
latchInstance.countDown();

子线程完成一个,则计数器-1

latchInstance.await();

这句之后的代码,会等待所有子线程执行完成后,才会继续往下执行,否则一直处于等待状态。


if (latchInstance.getCount() == 0) 

如果计数器变为0,则说明所有的子线程执行完成,会执行latchInstance.await();之后的语句

输出:
主线程开始执行…… ……
子线程:pool-1-thread-1执行
子线程:pool-1-thread-3执行
子线程:pool-1-thread-5执行
子线程:pool-1-thread-4执行
子线程:pool-1-thread-2执行
5个子线程都执行完毕,继续往下执行主线程 false
主线程开始执行…… ……
子线程:pool-1-thread-5执行
子线程:pool-1-thread-3执行
子线程:pool-1-thread-4执行
3个子线程都执行完毕,继续往下执行主线程 false
主线程开始执行…… ……
子线程:pool-1-thread-4执行
子线程:pool-1-thread-5执行
子线程:pool-1-thread-3执行
3个子线程都执行完毕,继续往下执行主线程 false
...

案例中通过单例模式来创建CountDownLatch实例。

4、总结

CountDownLatch 在创建实例时构造函数必须要传入一个子线程数量,且子线程数量的值需>0。
当 getCount() =0时,这时候你看到的只是一个普通多线程环境,所以需要重新传入子线程数量创建新CountDownLatch 实例。

你可能感兴趣的:(Java)