请看以下的代码
package rs.thread.day0504;
import java.util.concurrent.CountDownLatch;
/**
* @auther rs
* @date 2019/5/4 20:32
* @email 529811807@qq.com
* @weixinhao javawjs
*
*/
public class Test10_CountDownLatch {
public static void main(String [] args ){
//执行时间
long startTime = System.currentTimeMillis();
//这个是控制几个线程在执行后,在执行主线程的任务
int count = 6;
CountDownLatch ld = new CountDownLatch(count);
for(int j =0;j
new Thread(new CountDownLatchThread(ld)){}.start();
}
//等待数得到0这个数据在执行
try {
ld.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
long endTime = System.currentTimeMillis();
System.out.println("使用的时间是>>" + (endTime - startTime));
}
}
class CountDownLatchThread implements Runnable{
private CountDownLatch latch ;
public CountDownLatchThread(){}
public CountDownLatchThread(CountDownLatch latch){
this.latch = latch;
}
@Override
public void run() {
for(int i = 0;i<50000;i++){
if(i % 2 == 0){
System.out.println(i);
}
}
//必须控制它减少到0
latch.countDown();
}
}
看了上面的定义和Demo代码之后,使用就会简单一点了,一般流程如首先是创建实例 CountDownLatch countDown = new CountDownLatch(2)
需要同步的线程执行完之后,计数-1; countDown.countDown()
需要等待其他线程执行完毕之后,再运行的线程,调用 countDown.await()实现阻塞同步
注意在创建实例是,必须指定初始的计数值,且应大于0
必须有线程中显示的调用了countDown()计数-1方法;必须有线程显示调用了 await()方法(没有这个就没有必要使用CountDownLatch了)
由于await()方法会阻塞到计数为0,如果在代码逻辑中某个线程漏掉了计数-1,导致最终计数一直大于0,直接导致死锁了
鉴于上面一点,更多的推荐 await(long, TimeUnit)来替代直接使用await()方法,至少不会造成阻塞死只能重启的情况
应用场景
前面给了一个demo演示如何用,那这个东西在实际的业务场景中是否会用到呢?
因为确实在一个业务场景中使用到了,不然也就不会单独捞出这一节...
电商的详情页,由众多的数据拼装组成,如可以分成一下几个模块交易的收发货地址,销量
商品的基本信息(标题,图文详情之类的)
推荐的商品列表
评价的内容
....
上面的几个模块信息,都是从不同的服务获取信息,且彼此没啥关联;所以为了提高响应,完全可以做成并发获取数据,如线程1获取交易相关数据
线程2获取商品基本信息
线程3获取推荐的信息
线程4获取评价信息
....
但是最终拼装数据并返回给前端,需要等到上面的所有信息都获取完毕之后,才能返回,这个场景就非常的适合 CountDownLatch来做了在拼装完整数据的线程中调用 CountDownLatch#await(long, TimeUnit) 等待所有的模块信息返回
每个模块信息的获取,由一个独立的线程执行;执行完毕之后调用 CountDownLatch#countDown() 进行计数-1
CountDownLatch实现原理就是AQS 的原理
这么好用的功能是怎么实现的呢,下面就来说一说实现它的核心技术原理 AQS。 AQS 全称 AbstractQueuedSynchronizer,是 java.util.concurrent 中提供的一种高效且可扩展的同步机制。它可以用来实现可以依赖 int 状态的同步器,获取和释放参数以及一个内部FIFO等待队列,除了CountDownLatch,ReentrantLock、Semaphore 等功能实现都使用了它。
接下来用 CountDownLatch 来分析一下 AQS 的实现。建议看文章的时候先大致看一下源码,有助于理解下面所说的内容。
在我们的方法中调用 awit()和countDown()的时候,发生了几个关键的调用关系,我画了一个方法调用图。
首先在 CountDownLatch 类内部定义了一个 Sync 内部类,这个内部类就是继承自 AbstractQueuedSynchronizer 的。并且重写了方法 tryAcquireShared和tryReleaseShared。例如当调用 awit()方法时,CountDownLatch 会调用内部类Sync 的 acquireSharedInterruptibly() 方法,然后在这个方法中会调用 tryAcquireShared 方法,这个方法就是 CountDownLatch 的内部类 Sync 里重写的 AbstractQueuedSynchronizer 的方法。调用 countDown() 方法同理。
这种方式是使用 AbstractQueuedSynchronizer 的标准化方式,大致分为两步:
1、内部持有继承自 AbstractQueuedSynchronizer 的对象 Sync;
2、并在 Sync 内重写 AbstractQueuedSynchronizer protected 的部分或全部方法,这些方法包括如下几个:
总结
1、AQS 分为独占模式和共享模式,CountDownLatch 使用了它的共享模式。
2、AQS 当第一个等待线程(被包装为 Node)要入队的时候,要保证存在一个 head 节点,这个 head 节点不关联线程,也就是一个虚节点。
3、当队列中的等待节点(关联线程的,非 head 节点)抢到锁,将这个节点设置为 head 节点。
4、第一次自旋抢锁失败后,waitStatus 会被设置为 -1(SIGNAL),第二次再失败,就会被 LockSupport 阻塞挂起。
5、如果一个节点的前置节点为 SIGNAL 状态,则这个节点可以尝试抢占锁。
欢迎转载,转载请注明出处!
github: rs1314
欢迎关注共公众号微信 : java微技术
分享我的学习之路和各种java技术,教程资料