闭锁java_java多线程学习十::::CountDownLatch闭锁

请看以下的代码

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技术,教程资料

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值