当前位置 博文首页 > BattleHeart:CountDownLatch原理详解

    BattleHeart:CountDownLatch原理详解

    作者:BattleHeart 时间:2021-06-09 18:26

    CountDownLatch是一种同步辅助,让我们多个线程执行任务时,需要等待线程执行完成后,才能执行下面的语句,之前线程操作时是使用Thread.join方法进行等待,CountDownLatch内部使用了AQS锁,前面已经讲述过AQS的内部结构,其实内部有一个state字段,通过该字段来控制锁的操作,CountDownLatch是如何控制多个线程执行都执行结束?其实CountDownLatch内部是将state作为计数器来使用,比如我们初始化时,state计数器为3,同时开启三个线程当有一个线程执行成功,每当有一个线程执行完成后就将state值减少1,直到减少到为0时,说明所有线程已经执行完毕。

    介绍

    当你看到这篇文章的时候需要先了解AQS的原理,因为本文不涉及到AQS内部原理的讲解。

    CountDownLatch是一种同步辅助,让我们多个线程执行任务时,需要等待线程执行完成后,才能执行下面的语句,之前线程操作时是使用Thread.join方法进行等待,CountDownLatch内部使用了AQS锁,前面已经讲述过AQS的内部结构,其实内部有一个state字段,通过该字段来控制锁的操作,CountDownLatch是如何控制多个线程执行都执行结束?其实CountDownLatch内部是将state作为计数器来使用,比如我们初始化时,state计数器为3,同时开启三个线程当有一个线程执行成功,每当有一个线程执行完成后就将state值减少1,直到减少到为0时,说明所有线程已经执行完毕。

    源码解析

    以一个例子来开始进行源码解析,后面的内容会针对例子来进行源码的分解过程,我们开启三个线程,主线程需要等待三个线程都完成后才能进行后续任务处理,源码如下所示:

    public class CountDownLatchDemo {
        public static void main(String[] args) throws InterruptedException {
            // 计数器3个。
            CountDownLatch countDownLatch = new CountDownLatch(3);

            for (int i = 0; i < 3; ++i) {
                new Thread(new Worker(countDownLatch, i)).start();
            }
            // 等待三个线程都完成
            countDownLatch.await();
            System.out.println("3个线程全部执行完成");
        }

        // 搬运工人工作线程工作类。
        static class Worker implements Runnable {
            private final CountDownLatch countDown;
            private final Integer id;

            Worker(CountDownLatch countDown, Integer id) {
                this.countDown = countDown;
                this.id = id;
            }

            @Override
            public void run() {
                try {
                    Thread.sleep(500);
                    doWork();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                countDown.countDown();
                System.out.println("第" + id + "个线程执行完成工作");
            }

            void doWork() {
                System.out.println("第" + id + "个线程开始工作");
            }
        }
    }

    通过一个例子来说明一下CountDownLatch的工作原理,上面例子我们开启了三个线程,每个线程分别执行自己的任务,主线程等待三个线程执行完成,看一下输出的结果:

    等待三个线程完成
    第1个线程开始工作
    第0个线程开始工作
    第0个线程执行完成工作
    第1个线程执行完成工作
    第2个线程开始工作
    第2个线程执行完成工作
    3个线程全部执行完成

    这里我们将三个线程想象成搬运工人,将货物搬运到车上,三个人必须将自己手头分配的任务都搬运完成后才能触发,也即是货车司机需要等待三个人都完成才能发车,货车司机此时手里有个小本本,记录本次搬运的总人数,线程未启动时如下所示

    1.png
    1.png

    当搬运工人开始工作时,每个搬运工人各自忙碌自己的任务,假如当工人1完成后,需要跟司机报备一下,说我已经完成任务了,这时候司机会在自己的小本本上记录,工人1已经完成任务,此时还剩下两个工人没有完成任务。

    2.png
    2.png

    每当工人完成自己手头任务时,都会向司机报备,当所有工人都完成之后,此时工人的小本本记录的完成人数都已完成,司机这时候就可以发车了,因为三个人已经完成了搬运工作。

    3.png
    3.png

    通过上面的例子,大致了解了CountDownLatch的简单原理,如何保证司机(state)记录谁完成了谁没完成呢?CountDownLatch内部通过AQS的state来完成计数器的功能,接下来通过源码来进行详细分析:

    public class CountDownLatch {
        /**
         * 同步控制,
         * 使用 AQS的state来表示计数。
         */

        private static final class Sync extends AbstractQueuedSynchronizer {
            private static final long serialVersionUID = 4982264981922014374L;
            // 初始化state值(也就是需要等待几个线程完成任务)
            Sync(int count) {
                setState(count);
            }
            // 获取state值。
            int getCount() {
                return getState();
            }
            // 获得锁。
            protected int tryAcquireShared(int acquires) {
                // 这里判断如果state=0的时候才能获得锁,反之获取不到将当前线程放入到队列中阻塞。
                // 这里是关键点。
                return (getState() == 0) ? 1 : -1