[Java并发]CountDownLatch

CountDownLatch

CountDownLatch一般用作多线程倒计时计数器,强制它们等待其他一组(CountDownLatch的初始化决定)任务执行完成。
有一点要说明的是CountDownLatch初始化后计数器值递减到0的时候,不能再复原的,这一点区别于Semaphore,Semaphore是可以通过release操作恢复信号量的。

CountDownLatch 使用一个计数器,这个计数器初始化为一个给定的值。每次调用 countDown() 方法时,计数器的值就减一。当计数器的值减到零时,所有等待在这个 CountDownLatch 上的线程都会被唤醒并继续执行。

countDownLatch.await(countDownLatchTimeout, TimeUnit.MINUTES);

await(long timeout, TimeUnit unit) 方法
这个方法使当前线程等待,直到计数器到达零或者指定的等待时间过去。这个方法带有两个参数:

timeout:等待的最大时间。
unit:时间单位,可以是 TimeUnit 枚举中的一个值,例如 SECONDS、MINUTES 等。

CountDownLatch使用原理#

  • 创建CountDownLatch并设置计数器值。
  • 启动多线程并且调用CountDownLatch实例的countDown()方法。
  • 主线程调用 await() 方法,这样主线程的操作就会在这个方法上阻塞,直到其他线程完成各自的任务,count值为0,停止阻塞,主线程继续执行。
public class Main {
    public static void main(String[] args) {
        //创建CountDownLatch并设置计数值,该count值可以根据线程数的需要设置
        CountDownLatch countDownLatch = new CountDownLatch(10);

        //创建线程池
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

        for (int i = 0; i < 10; i++) {
            cachedThreadPool.execute(() -> {
                try {
                    System.out.println(Thread.currentThread().getName() + " 正在运行!");
                } catch (Exception e) {
                    System.out.println("Exception: do something exception");
                } finally {
                    //该线程执行完毕-1
                    System.out.println("计数器-1 = " + countDownLatch.getCount());
                    countDownLatch.countDown();

                }
            });
        }

        try {
            System.out.println("主线程试图执行");
            countDownLatch.await(1, TimeUnit.MINUTES);
            System.out.println("主线程恢复执行");
        } catch (InterruptedException e) {
            System.out.println("Exception: await interrupted exception");
        } finally {
            System.out.println("主线程执行结束");
        }

        //若需要停止线程池可关闭;
        cachedThreadPool.shutdown();
    }
}
主线程试图执行
pool-1-thread-2 正在运行!
pool-1-thread-1 正在运行!
pool-1-thread-3 正在运行!
pool-1-thread-4 正在运行!
计数器-1 = 10
计数器-1 = 10
计数器-1 = 10
计数器-1 = 10
pool-1-thread-5 正在运行!
计数器-1 = 6
pool-1-thread-6 正在运行!
计数器-1 = 5
pool-1-thread-7 正在运行!
pool-1-thread-8 正在运行!
计数器-1 = 4
计数器-1 = 4
pool-1-thread-9 正在运行!
计数器-1 = 2
pool-1-thread-10 正在运行!
计数器-1 = 1
主线程恢复执行
主线程执行结束

CountDownLatch常用方法#

  • public void await() throws InterruptedException:调用await()方法的线程会被挂起,等待直到count值为0再继续执行。

  • public boolean await(long timeout, TimeUnit unit) throws InterruptedException:同await(),若等待timeout时长后,count值还是没有变为0,不再等待,继续执行。时间单位如下常用的毫秒、天、小时、微秒、分钟、纳秒、秒。

  • public void countDown(): count值递减1.

  • public long getCount():获取当前count值。

  • public String toString():重写了toString()方法,多打印了count值,具体参考源码。

CountDownLatch使用场景#

一个程序中有N个任务在执行,我们可以创建值为N的CountDownLatch,当每个任务完成后,调用一下countDown()方法进行递减count值,再在主线程中使用await()方法等待任务执行完成,主线程继续执行。

CountDownLatch源码#

构造方法源码
    /**
     * Constructs a {@code CountDownLatch} initialized with the given count.
     *
     * @param count the number of times {@link #countDown} must be invoked
     *        before threads can pass through {@link #await}
     * @throws IllegalArgumentException if {@code count} is negative
     */
    public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }
toString()方法源码
    /**
     * Returns a string identifying this latch, as well as its state.
     * The state, in brackets, includes the String {@code "Count ="}
     * followed by the current count.
     *
     * @return a string identifying this latch, as well as its state
     */
    public String toString() {
        return super.toString() + "[Count = " + sync.getCount() + "]";
    }

CountDownLatch示例#

作为线程启动信号#

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

        CountDownLatch startSignal = new CountDownLatch(1);
        CountDownLatch doneSignal = new CountDownLatch(10);

        for (int i = 0; i < 10; i++) {
            // create and start threads
            new Thread(new Worker(startSignal, doneSignal)).start();
        }


        System.out.println("主线程将countdown置为0");
        // 主线程发令
        startSignal.countDown();
        System.out.println("主线程等待十个线程完成");
        doneSignal.await();
        System.out.println("主线程恢复执行");
    }

    static class Worker implements Runnable {

        private final CountDownLatch startSignal;
        private final CountDownLatch doneSignal;

        Worker(CountDownLatch startSignal, CountDownLatch doneSignal) {
            this.startSignal = startSignal;
            this.doneSignal = doneSignal;
        }

        @Override
        public void run() {
            try {
                // 所有子线程等待countdown发令
                System.out.println(Thread.currentThread().getName()+"子线程等待主线程发令");
                startSignal.await();
                doWork();
                doneSignal.countDown();
            } catch (InterruptedException ex) {
                ex.printStackTrace();
            }
        }

        void doWork() {
            System.out.println(Thread.currentThread().getName()+"-执行中");
        }
    }
}

主线程将countdown置为0
主线程等待十个线程完成
Thread-1子线程等待主线程发令
Thread-1-执行中
Thread-0子线程等待主线程发令
Thread-0-执行中
Thread-2子线程等待主线程发令
Thread-2-执行中
Thread-3子线程等待主线程发令
Thread-3-执行中
Thread-4子线程等待主线程发令
Thread-4-执行中
Thread-5子线程等待主线程发令
Thread-5-执行中
Thread-7子线程等待主线程发令
Thread-7-执行中
Thread-8子线程等待主线程发令
Thread-8-执行中
Thread-9子线程等待主线程发令
Thread-9-执行中
Thread-6子线程等待主线程发令
Thread-6-执行中
主线程恢复执行

从运行结果可以看出:

主线程先打印 主线程将countdown置为0 和 主线程等待十个线程完成 。因为startSignal.countDown();完后,count才为0,子线程才能打印。

因为startSignal.await();是在子线程内,所有子线程都等待startSignal.countDown()执行后才能打印do work!。

doneSignal.await();等待所有子线程执行后,每次都doneSignal.countDown(),最后count为0,主线程才执行打印wait for all to finsh。

作为线程等待完成信号#

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

        CountDownLatch doneSignal = new CountDownLatch(5);

        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            cachedThreadPool.execute(new Worker(doneSignal));
        }

        System.out.println("主线程等待执行");
        doneSignal.await();
        System.out.println("主线程恢复执行");
        cachedThreadPool.shutdown();
    }

    static class Worker implements Runnable{

        private final CountDownLatch doneSignal;

        Worker(CountDownLatch doneSignal) {
            this.doneSignal = doneSignal;
        }

        @Override
        public void run() {
            try {
                doWork();
                doneSignal.countDown();
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }

        void doWork() {
            System.out.println(Thread.currentThread().getName() + "---执行---");
        }
    }

}


运行结果

主线程等待执行
pool-1-thread-2---执行---
pool-1-thread-1---执行---
pool-1-thread-3---执行---
pool-1-thread-4---执行---
pool-1-thread-5---执行---
主线程恢复执行
pool-1-thread-6---执行---
pool-1-thread-7---执行---
pool-1-thread-8---执行---
pool-1-thread-9---执行---
pool-1-thread-10---执行---

从运行结果可以看出,主线程是等待其他线程运行了5次结束后就打印了 主线程恢复执行 信息,因为CountDownLatch数值为5。

作者:Esofar

出处:https://www.cnblogs.com/DCFV/p/18326250

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   Duancf  阅读(13)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
more_horiz
keyboard_arrow_up light_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示