`

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实现闭锁)

    本文将详细介绍如何利用MyBatis结合多线程和CountDownLatch闭锁来实现数据的批量插入。 首先,我们来看`mybatis批处理`。MyBatis的批处理功能允许我们在一次数据库连接中执行多条SQL语句,从而减少了数据库连接的...

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

    CountDownLatch是Java并发编程中的一个重要工具,它属于Java并发包`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并发工具包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 ...

    并发编程之CountDownLatch

    主线程必须在启动其他线程后立即调用 CountDownLatch.await()方法,其他 N 个线程必须引用闭锁对象,因为他们需要通知 CountDownLatch 对象,他们已经完成了各自的任务。 在实际开发中,CountDownLatch 可以用来...

    Java并发系列之CountDownLatch源码分析

    Java并发系列之CountDownLatch源码分析 CountDownLatch是一种非常有用的工具类,用于拦截一个或多个线程,使其在某个条件成熟后再执行。它的内部提供了一个计数器,在构造闭锁时必须指定计数器的初始值,且计数器的...

    CountDownLatch、Semaphore等4大并发工具类详解

    本文将详细介绍 Java 并发工具类的四大类:CountDownLatch、Semaphore、CyclicBarrier 和 Phaser,及其应用场景和使用方法。 CountDownLatch CountDownLatch 是一个同步的辅助类,允许一个或多个线程,等待其他一...

    Java并发编程之闭锁与栅栏的实现

    闭锁,由`java.util.concurrent.CountDownLatch`类实现,它是一个计数器,初始化时传入一个整数count,表示需要等待的线程数量。每当一个线程完成其任务时,会调用`countDown()`方法,将计数器减一。所有线程完成...

    实例讲解Java并发编程之闭锁

    Java并发编程中的闭锁是一种同步工具类,它用于协调多个线程之间的操作顺序,确保一组操作在所有线程完成之前不会继续。闭锁的核心概念是一个内部计数器,初始值为一个正整数,表示需要等待的事件数量。当这个计数器...

    深入学习Java同步机制中的底层实现

    `CountDownLatch`则是一种闭锁,常用于多线程间的协调,它允许一个或多个线程等待其他线程完成操作。`CountDownLatch`的`Sync`子类同样基于AQS,通过`tryAcquireShared`和`tryReleaseShared`来实现计数器的递减与...

    JAVA中锁的解决方案.docx

    CountDownLatch cdl = new CountDownLatch(5000); for (int i = 0; i ; i++) { es.execute(() -&gt; { test.increment(); cdl.countDown(); }); } es.shutdown(); try { // 等待 5000 个任务执行完成后,打印...

    深入浅出_Java并发工具包原理讲解

    3. 各种并发工具类:包括闭锁(CountDownLatch)、栅栏(CyclicBarrier)、信号量(Semaphore)、读写锁(ReentrantReadWriteLock)等,这些工具类提供了丰富的线程间协调和控制的机制,使得多线程之间的合作更加...

    Java并发编程之栅栏(CyclicBarrier)实例介绍

    与闭锁(CountDownLatch)不同,闭锁通常是一次性的,而CyclicBarrier可以重用,即“循环”在每次所有线程都到达屏障点后重新初始化。 1. **CyclicBarrier的基本概念**: - **屏障点**:CyclicBarrier的核心是屏障...

    你会用Java代码模拟高并发吗

    本文将介绍如何使用 Java 代码模拟高并发,通过示例代码详细讲解了使用 Semaphore 和 CountDownLatch 两个类来模拟高并发的效果。 Semaphore Semaphore 是一种基于计数的信号量,可以设定一个阈值,基于此,多个...

    java经典面试题

    - `CountDownLatch`:倒计时闭锁。 28. **`wait()` 和 `sleep()` 的区别**: - `wait()` 会释放对象的锁,而 `sleep()` 不会。 - `wait()` 需要在同步上下文中使用,而 `sleep()` 可以在任何地方使用。 29. **`...

    Java判断线程池线程是否执行完毕

    CountDownLatch是Java中的一种闭锁实现,可以用来等待一组事件的发生。在本例中,我们使用CountDownLatch来实现线程池中所有子线程的执行完成判断。下面是一个示例程序: ```java public class ...

Global site tag (gtag.js) - Google Analytics