`

java闭锁—CountDownLatch

阅读更多

前言

 

前文《java同步器--AQS》中提到,AQS是构建java.util.concurrent包中同步阻塞工具类的基础。这次来看下使用AQS实现的java闭锁—CountDownLatch,它可以阻塞一个或多个线程,以等待另一组事件的发生后,继续执行被阻塞的一个或多个线程。CountDownLatch的两个核心方法:调用await方法阻塞一个或多个线程;调用countDown方法,执行一组事件,每调用一次对“资源”数减1,当剩余“资源”数为0时,被阻塞的一个或多个线程同时被唤醒。这其实就是AQS的共享方式实现,在分析CountDownLatch实现原理之前,先来简单看看CountDownLatch的使用。

 

这有点类似游戏里组队玩游戏,假设游戏需要10个人,每个人加入游戏时都会被阻塞在开始界面,只有等人满后才能点击开始游戏。模拟代码如下:

 
/**
 * Created by gantianxing on 2018/1/3.
 */
public class CountDownLatchTest {
 
    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        CountDownLatch playersCounter = new CountDownLatch(10);
        for (int i=0;i<10;i++){
            executorService.submit(new Player(playersCounter,i+""));
        }
 
        System.out.println("等待玩家加入游戏");
        //这里只模拟了一个线程阻塞,可以多线程调用await()阻塞
        playersCounter.await();
        System.out.println("游戏开始");
 
        executorService.shutdown();//关闭线程池
        System.out.println("游戏结束");
    }
}
 
//多线程操作线程不安全容器 ThreadSafe.datas
class Player implements Runnable{
    private CountDownLatch playersCounter;
    private String name;
 
    public Player(CountDownLatch playersCounter,String name) {
        this.playersCounter = playersCounter;
        this.name = name;
    }
 
    @Override
    public void run() {
        System.out.println("玩家:"+name+"加入游戏");
        playersCounter.countDown();//对剩余的游戏坑位减1
    }
}
 

 

执行上述main方法,可以发现只有等10个玩家都准备就绪后,游戏才能开始。可以发现使用CountDownLatch很简单,但我们不能仅仅停留在如何使用上,还应该更进一步了解其内部原理,以便再必要的时候创建自己的同步器。

 

CountDownLatch实现原理解析

 

在已经全面理解了AQS的前提下(见前一篇文章),再来看CountDownLatch内部实现,你会觉得非常简单。其内部核心实现就是,定义了一个实现了AQS的内部类SyncAQS的公共资源状态字段state的值,就是初始化CountDownLatch(int count)count值;调用CountDownLatchawait方法,会判断state的值是否变为0,如果不为0就阻塞该线程;调用CountDownLatchcountDown方法,没调用一次会对state1,直到为0时,唤醒所有被阻塞的线程。

 

首先来看下内部类Sync的实现,AQS预留给子类实现的方法分为两类:排它和共享,排它 获取和释放方法分别为tryAcquirtryRelease;共享 获取和释放方法分别为tryAcquireSharedtryReleaseShared。如果把CountDownLatch理解为锁的话,它属于共享锁的一种,因为所有阻塞的线程共享同一个开关,所以在CountDownLatchSyncAQS的实现,应该是共享实现,也就是说实现了tryAcquireSharedtryReleaseShared方法。我们来看下源码:

private static final class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = 4982264981922014374L;
 
    Sync(int count) {
        setState(count);//设置AQS的队列状态state字段
    }
 
    int getCount() {
        return getState();
    }
 
    protected int tryAcquireShared(int acquires) {
              //判断AQS的队列状态state值是否变为0,如果不为0,阻塞该线程
        return (getState() == 0) ? 1 : -1;
    }
 
    protected boolean tryReleaseShared(int releases) {
        // Decrement count; signal when transition to zero
        for (;;) {
            int c = getState();
            if (c == 0)
                return false;
                      //尝试对state字段减1
            int nextc = c-1;
            if (compareAndSetState(c, nextc))//CAS原子修改state值
                return nextc == 0;
        }
    }
}

主要的方法就是构造方法Sync(int count)、获取资源方法tryAcquireShared(int acquires)、释放资源方法tryReleaseShared(int releases)。这三个方法分别会被CountDownLatch调用:

 

CountDownLatch构造方法

CountDownLatch的构造方法会调用Sync的构造方法Sync(int count),为AQSstate赋值;

public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);// 调用Sync的构造方法Sync(int count)
    }
 

 

CountDownLatchawait方法

CountDownLatchawait方法会调用SynctryAcquireShared方法,尝试获取锁,如果获取不到就阻塞;

public void await() throws InterruptedException {
        //AQS的acquireSharedInterruptibly方法内部会调用tryAcquireShared方法
        sync.acquireSharedInterruptibly(1);
    }
 

 

CountDownLatchcountDown方法

CountDownLatchcountDown方法会调用SynctryReleaseShared方法释放资源,当state值变为0时,唤醒所有被阻塞的线程。

public void countDown() {
        sync.releaseShared(1);//没调用一次,就会对AQS的state字段减1
}
 

 

另外CountDownLatchawait方法还有延迟版本:

public boolean await(long timeout, TimeUnit unit)
        throws InterruptedException {
        return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}

 

对应CountDownLatch的两个await方法,也许你已经注意到了 它们都会抛出InterruptedException异常,说明CountDownLatch实现的闭锁是可中断锁,调用线程的interrupt方法,可以中断阻塞的线程。

 

总结

 

 

简单的总结CountDownLatch闭锁就是:它实现了一个“共享锁”,可以阻塞一个或多个线程,以等待另一组事件的发生后,继续执行被阻塞的一个或多个线程。其核心实现就是基于AQS,另外CountDownLatch闭锁是可中断锁

 

 

 

0
0
分享到:
评论

相关推荐

    mybaits 多线程 实现数据批量插入 (运用CountDownLatch实现闭锁)

    mybaits 多线程 实现数据批量插入 (运用CountDownLatch实现闭锁) 1、mybatis批处理 2、数据分批量查询 3、数据分批量插入

    J.U.C-AQS框架同步组件之闭锁CountDownLatch介绍

    CountDownLatch是在java1.5被引入的,跟它一起被引入的并发工具类还有CyclicBarrier、Semaphore、ConcurrentHashMap和BlockingQueue,它们都存在于java.util.concurrent包下。CountDownLatch这个类能够使一个线程...

    java并发工具包 java.util.concurrent中文版用户指南pdf

    12. 闭锁 CountDownLatch 13. 栅栏 CyclicBarrier 14. 交换机 Exchanger 15. 信号量 Semaphore 16. 执行器服务 ExecutorService 17. 线程池执行者 ThreadPoolExecutor 18. 定时执行者服务 ScheduledExecutorService ...

    Java并发工具包java.util.concurrent用户指南中英文对照阅读版.pdf

    闭锁 CountDownLatch 13. 栅栏 CyclicBarrier 14. 交换机 Exchanger 15. 信号量 Semaphore 16. 执行器服务 ExecutorService 17. 线程池执行者 ThreadPoolExecutor 18. 定时执行者服务 ScheduledExecutorService 19....

    java并发工具包详解

    12. 闭锁 CountDownLatch 13. 栅栏 CyclicBarrier 14. 交换机 Exchanger 15. 信号量 Semaphore 16. 执行器服务 ExecutorService 17. 线程池执行者 ThreadPoolExecutor 18. 定时执行者服务 ScheduledExecutorService ...

    Java并发包之CountDownLatch用法.docx

    CountDownLatch计数器闭锁是一个能阻塞主线程,让其他线程满足特定条件下主线程再继续执行的线程同步工具。 Latch闭锁的意思,是一种同步的工具类。类似于一扇门:在闭锁到达结束状态之前,这扇门一直是关闭着的,不...

    Java并发工具包java.util.concurrent用户指南中英文对照阅读版

    12. 闭锁 CountDownLatch 13. 栅栏 CyclicBarrier 14. 交换机 Exchanger 15. 信号量 Semaphore 16. 执行器服务 ExecutorService 17. 线程池执行者 ThreadPoolExecutor 18. 定时执行者服务 ScheduledExecutorService ...

    java并发包资源

    12. 闭锁 CountDownLatch 13. 栅栏 CyclicBarrier 14. 交换机 Exchanger 15. 信号量 Semaphore 16. 执行器服务 ExecutorService 17. 线程池执行者 ThreadPoolExecutor 18. 定时执行者服务 ScheduledExecutorService ...

    并发编程笔记20190526.docx

    二、闭锁CountDownLatch 28 1、应用场景 28 2、CyclicBarrier 28 3、Semaphore 29 4、Callable、Future和FutureTask 30 5、原子操作CAS (compare atomic swap) 32 三、显式锁和AQS 34 1、AQS定义两种资源共享方式: ...

    Java并发编程应该掌握的并发工具类,快来看看你掌握了哪些?

    在我们JDK的并发包中,提供了几个非常有用的并发工具类,比如:CountDownLatch 闭锁、CyclicBarrier 同步屏障、Semaphore 信号量,在线程之间交换数据的一种方式 Exchanger,赶紧操练起来。 2、CountDownLatch 闭锁 ...

    javaconcurrent源码-java7-source-code:Java7源码/Concurrency同步

    CountDownLatch 闭锁 AQS 锁的公共类 20180514 String, 部分Character 20180508 除 Set 外, 常用的 Collection 都已经分析完毕 简化语言描述, 增加测试用例(实践用法) 接触到新的类再看源码(不能脱离实际场景瞎看, ...

    Java并发编程(学习笔记).xmind

    CountDownLatch:可以使一个或多个线程等待一组事件发生 FutureTask *应用场景 (1)用作异步任务使用,且可以使用get方法获取任务的结果 (2)用于表示一些时间较长的计算 状态 ...

    尚硅谷Java视频_JUC 视频教程

    尚硅谷_JUC线程高级_CountDownLatch 闭锁 ·6. 实现 Callable 接口 ·7. 尚硅谷_JUC线程高级_同步锁 Lock ·8. 尚硅谷_JUC线程高级_生产者消费者案例-虚假唤醒 ·9. 尚硅谷_JUC线程高级_Condition 线程通信 ·10. ...

    Java并发编程实战

    14.6.2 Semaphore与CountDownLatch258 14.6.3 FutureTask259 14.6.4 ReentrantReadWriteLock259 第15章 原子变量与非阻塞同步机制261 15.1 锁的劣势261 15.2 硬件对并发的支持262 15.2.1 比较并交换263 ...

    java8源码-baijia123:常用工具类及测试类

    TestHarnes-&gt;在计时测试中使用CountDownLatch(闭锁)来启动和停止线程 Preloader-&gt;使用FutureTask来提前加载稍后需要的数据 BoundedHashSet-&gt;使用Semaphore为容器设置边界 CellularAutomata-&gt;通过CyclicBarrier...

    juconcurrent:java.util.concurrent

    原子变量-CAS算法3.ConcurrentHashMap锁分段机制4.CountDownLatch闭锁5.实现Callable接口6.Lock同步锁7.Condition控制线程通信8.线程按序交替9.ReadWriteLock读写锁10.线程八锁11.线程池12.线程调度13.ForkJoinPool ...

Global site tag (gtag.js) - Google Analytics