简介且高效,从源码的角度带你理解CountDownLatch的设计原理。下次换你来拷打面试官!

大家好,我是程序员牛肉。今天来带大家一起从源码的角度学习一下JUC中的一个热门工具类:CountDownLatch。首先我们要来介绍一下什么是CountDownLatch:在多线程任务中,我们经常会遇到这样一种情况:我们需要先执行A任务和B

简介且高效,从源码的角度带你理解CountDownLatch的设计原理。下次换你来拷打面试官!

大家好,我是程序员牛肉。

今天来带大家一起从源码的角度学习一下JUC中的一个热门工具类:CountDownLatch。

首先我们要来介绍一下什么是CountDownLatch:

在多线程任务中,我们经常会遇到这样一种情况:我们需要先执行A任务和B任务。只有AB任务都执行完了才可以执行C任务。

比如说在微服务架构的订单体系中,当用户下单之后,我们需要先在库存中进行扣减,并且同时到营销服务中核对优惠劵。之后才会到运单服务中将订单转为运单。

那我们要如何实现两个任务相互进行等待,只有两个任务都执行成功之后才会进行下一步呢?

我说白了,这些东西没那么高端。我直接一个join过去就能实现这个功能。但是这样实在是太low了!有没有更加高效的方法呢?

有的有的兄弟,CountDownLatch就是用来解决着这种场景的:

代码语言:javascript代码运行次数:0运行复制
        CountDownLatch latch = new CountDownLatch(2);

        new Thread(() -> {
            System.out.println("库存服务开始执行...");
            try { Thread.sleep(2000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
            System.out.println("库存服务执行完成");
            latch.countDown();
        }).start();

        new Thread(() -> {
            System.out.println("营销服务开始执行...");
            try { Thread.sleep(3000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
            System.out.println("营销服务执行完成");
            latch.countDown();
        }).start();

        try {
            latch.await();
            System.out.println("库存服务和营销服务都已完成,开始执行运单服务");
            executeShippingService();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    private static void executeShippingService() {
        System.out.println("运单服务开始执行...");
        try { Thread.sleep(1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
        System.out.println("运单服务执行完成");
    }
}

其实CountDownLatch的原理没多高端。你可以理解为我们手动为维护了一个计数器。

在上述代码中,我们先创建了一个初始值为2的计数器

在执行前置的库存服务和营销服务的时候,每当这两个服务执行完的时候,就会对这个计数器做减一的操作。

当这个计数器为0的时候,我们就会进一步执行运单服务。

那在讲完了大致思想之后,我们就来深入的看一看源码部分吧。

我们可以看到,当我们使用CountDownLatch latch = new CountDownLatch(2)来创建计数器的时候,实际上这个数字被传递给了Sync这个类。

所以让我们追一下Sync这个类:

这一看就豁然开朗了,Sync继承了AQS这个线程同步抽象类。而CountDownLatch又是基于Sync去实现的。

当我们向CountDownLatch中传递一个变量的时候,其实就是基于Sync创建了一个共享变量state。

而CountDownLatch的countDown其实就是执行了syn包中的releaseShared方法:

因此我们来看一看这个方法的具体实现:

这一看太简单了,就是对我们之前构造的那个state参数进行减一的操作。那么唤醒主线程的await方法呢?

await方法中调用了syn的acquireSharedInterruptibly方法。

而AQS的这个方法是final类型修饰的,因此sync没有能力重写这个类,而是直接调用的AQS原生的方法:

首先:sync.acquireSharedInterruptibly方法传入的参数是1,意思是请求共享锁。

之后在acquireSharedInterruptibly方法中,先检查当前线程是否被中断,如果被中断了就直接抛异常。

之后调用了tryAcquireShared方法来判断之前设置的state的状态:

如果state为0的话(子任务已经全部执行完毕),就返回1,如果state不为0的话,就返回-1。

当state不为0的时候,说明子任务还没有执行完,就继续使用acquire方法将当前线程加入到阻塞队列中:

在acquire中,会不断的使用自旋重试的方法来检查state的值:

所以acquire的作用就是以自旋的方式来阻塞主线程,直到state为空之后,主线程才会从阻塞队列中弹出,继续往下执行。

所以整体看来CountDownLatch的源码还是比较简洁的,它就是维护了一个计数器。子线程在执行任务的时候,会对计数器进行减一的操作。而主线程会被AQS阻塞,不断的去重试这个计数器的值是否为0,只有计数器为0,主线程才会继续执行后续的方法

而由于CountDownLatch整体的设计思路中state要用来标识状态信息。因此要保证其是线程安全的。所以syn类并没有对外提供重置state变量的方法:

也就是说每一个CountDownLatch只能使用一次。每一次计数器为0之后,你都需要重新创建一个CountDownLatch。

因此其实当大家出现这种多线程编排的时候,其实我更加推荐大家使用Completablefuture的allof方法。可以看一看我之前写的这一篇文章学习一下这个类:

下次换你来拷打面试官!一文带你读懂企业常用异步编程核心工具类CompletableFuture

2025-01-29

今天的文章就聊到这里了,相信通过我的介绍,你已经大致了解了CountDownLatch的设计思路。希望我的文章可以帮到你。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。原始发表:2025-03-29,如有侵权请联系 cloudcommunity@tencent 删除原理源码服务设计线程

发布者:admin,转转请注明出处:http://www.yc00.com/web/1748063757a4726093.html

相关推荐

  • 简介且高效,从源码的角度带你理解CountDownLatch的设计原理。下次换你来拷打面试官!

    大家好,我是程序员牛肉。今天来带大家一起从源码的角度学习一下JUC中的一个热门工具类:CountDownLatch。首先我们要来介绍一下什么是CountDownLatch:在多线程任务中,我们经常会遇到这样一种情况:我们需要先执行A任务和B

    3小时前
    10

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信