大家好,我是程序员牛肉。
今天来带大家一起从源码的角度学习一下JUC中的一个热门工具类:CountDownLatch。
首先我们要来介绍一下什么是CountDownLatch:
在多线程任务中,我们经常会遇到这样一种情况:我们需要先执行A任务和B任务。只有AB任务都执行完了才可以执行C任务。
比如说在微服务架构的订单体系中,当用户下单之后,我们需要先在库存中进行扣减,并且同时到营销服务中核对优惠劵。之后才会到运单服务中将订单转为运单。
那我们要如何实现两个任务相互进行等待,只有两个任务都执行成功之后才会进行下一步呢?
我说白了,这些东西没那么高端。我直接一个join过去就能实现这个功能。但是这样实在是太low了!有没有更加高效的方法呢?
有的有的兄弟,CountDownLatch就是用来解决着这种场景的:
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
今天的文章就聊到这里了,相信通过我的介绍,你已经大致了解了CountDownLatch的设计思路。希望我的文章可以帮到你。
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有