1 概述
CountDownLatch是在java1.5被引入的,跟它一起被引入的并发工具类还有CyclicBarrier、Semaphore、ConcurrentHashMap,BlockingQueue,它们都存在于java.util.concurrent包下。
CountDownLatch是一个同步辅助类,通过AQS实现的一个闭锁。
在其他线程完成它们的操作之前,允许一个或多个线程等待。例如,应用程序的主线程希望在负责启动框架服务的线程已经启动所有的框架服务之后再执行。
CountDownLatch是通过一个计数器来实现的,计数器的初始值为线程的数量。每当一个线程完成了自己的任务后,计数器的值就会减1。当计数器值到达0时,它表示所有的线程已经完成了任务,然后在闭锁上等待的线程就可以恢复执行任务。
运行机制
2 数据结构
继承关系
CountDownLatch
是一个共享锁
,内部定义了自己的同步器Sync
,Sync
继承自AQS,实现了tryAcquireShared
和tryReleaseShared
两个方法。
CountDownLatch中的锁是响应中断的,如果线程在对锁进行操作期间发生中断,会直接抛出InterruptedException
3 源码解析
3.0 构造函数
构造器中的计数值(count)实际上就是闭锁需要等待的线程数量
该值只能被设置一次,CountDownLatch没有提供任何机制去重新设置这个计数值
CountDownLatch中的count其实就是AQS的state
从构造函数中可以看出,CountDownLatch
"锁计数"本质上就是AQS的资源数state
下面我们将通过await()
和countDown()
分析CountDownLatch的"latch"原理
3.1 await()
通过对资源state
剩余量state==0 ? 1 : -1
来判断是否获取到锁
tryAcquireShared
函数规定返回值类型
成功获取并且还有可用资源返回正数
成功获取但是没有可用资源时返回0
获取资源失败返回一个负数
即只要state!=0
,线程就进入等待队列阻塞
3.2 countDown()
其他N 个线程必须引用闭锁对象,因为他们需要通知CountDownLatch,他们已经完成了各自的任务
这种通知机制是通过CountDownLatch.countDown()
完成
每调用该方法,在构造函数中初始化的count值就减1
所以当N个线程都调 用了这个方法,count的值等于0,然后主线程就能通过await()方法,恢复执行自己的任务
如果释放资源后state==0
,说明已经到达latch,此时就可以调用doReleaseShared
唤醒等待的线程。
相对其他同步类来说,CountDownLatch可以说是最简单的同步类实现了。它完全依赖了AQS,只要理解了AQS,那么理解它就不成问题
4 在实时系统中的使用场景
实现最大的并行性
有时我们想同时启动多个线程,实现最大程度的并行性
例如,我们想测试一个单例类。如果我们创建一个初始计数为1的CountDownLatch,并让所有线程都在这个锁上等待,那么我们可以很轻松地完成测试。我们只需调用 一次countDown()方法就可以让所有的等待线程同时恢复执行。
开始执行前等待n个线程完成各自任务
例如应用程序启动类要确保在处理用户请求前,所有N个外部系统已经启动和运行了。
死锁检测
一个非常方便的使用场景是,你可以使用n个线程访问共享资源,在每次测试阶段的线程数目是不同的,并尝试产生死锁
5 CountDownLatch使用例子
在这个例子中,我模拟了一个应用程序启动类,它开始时启动了n个线程类,这些线程将检查外部系统并通知闭锁,并且启动类一直在闭锁上等待着。一旦验证和检查了所有外部服务,那么启动类恢复执行。
BaseHealthChecker.java:这个类是一个Runnable,负责所有特定的外部服务健康的检测。它删除了重复的代码和闭锁的中心控制代码。
public abstract class BaseHealthChecker implements Runnable { private CountDownLatch _latch; private String _serviceName; private boolean _serviceUp; //Get latch object in constructor so that after completing the task, thread can countDown() the latch public BaseHealthChecker(String serviceName, CountDownLatch latch) { super(); this._latch = latch; this._serviceName = serviceName; this._serviceUp = false; } @Override public void run() { try { verifyService(); _serviceUp = true; } catch (Throwable t) { t.printStackTrace(System.err); _serviceUp = false; } finally { if(_latch != null) { _latch.countDown(); } } } public String getServiceName() { return _serviceName; } public boolean isServiceUp() { return _serviceUp; } //This methos needs to be implemented by all specific service checker public abstract void verifyService(); }
NetworkHealthChecker.java:这个类继承了BaseHealthChecker,实现了verifyService()方法。DatabaseHealthChecker.java和CacheHealthChecker.java除了服务名和休眠时间外,与NetworkHealthChecker.java是一样的。
public class NetworkHealthChecker extends BaseHealthChecker{ public NetworkHealthChecker (CountDownLatch latch) { super("Network Service", latch); } @Override public void verifyService() { System.out.println("Checking " + this.getServiceName()); try { Thread.sleep(7000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(this.getServiceName() + " is UP"); } }
ApplicationStartupUtil.java:这个类是一个主启动类,它负责初始化闭锁,然后等待,直到所有服务都被检测完。
public class ApplicationStartupUtil{ //List of service checkers private static List<BaseHealthChecker> _services; //This latch will be used to wait on private static CountDownLatch _latch; private ApplicationStartupUtil() { } private final static ApplicationStartupUtil INSTANCE = new ApplicationStartupUtil(); public static ApplicationStartupUtil getInstance() { return INSTANCE; } public static boolean checkExternalServices() throws Exception { //Initialize the latch with number of service checkers _latch = new CountDownLatch(3); //All add checker in lists _services = new ArrayList<BaseHealthChecker>(); _services.add(new NetworkHealthChecker(_latch)); _services.add(new CacheHealthChecker(_latch)); _services.add(new DatabaseHealthChecker(_latch)); //Start service checkers using executor framework Executor executor = Executors.newFixedThreadPool(_services.size()); for(final BaseHealthChecker v : _services) { executor.execute(v); } //Now wait till all services are checked _latch.await(); //Services are file and now proceed startup for(final BaseHealthChecker v : _services) { if( ! v.isServiceUp()) { return false; } } return true; } }
现在你可以写测试代码去检测一下闭锁的功能了。
public class Main { public static void main(String[] args) { boolean result = false; try { result = ApplicationStartupUtil.checkExternalServices(); } catch (Exception e) { e.printStackTrace(); } System.out.println("External services validation completed !! Result was :: "+ result); } }
Output in console:
Checking Network Service Checking Cache Service Checking Database Service Database Service is UP Cache Service is UP Network Service is UP External services validation completed !! Result was :: true
作者:芥末无疆sss
链接:https://www.jianshu.com/p/7938f6543973
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
共同学习,写下你的评论
评论加载中...
作者其他优质文章