Java 并发编程CountDownLatch的应用与源码解析

导读:本篇文章讲解 Java 并发编程CountDownLatch的应用与源码解析,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

应用场景

CountDownLatch是一个多线程控制工具。用来控制线程的等待。

设置需要countDown的数量,然后每一个线程执行完毕后调用countDown()方法,而在主线程中调用await()方法等待,直到num个子线程执行了countDown()方法,则主线程开始继续执行

示例:

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CountDownLatchDemo {

    private static final int nums = 5;
    public static CountDownLatch countDownLatch = new CountDownLatch(nums);

    public static void main(String[] args) throws Throwable {
        ExecutorService es = Executors.newFixedThreadPool(5);
        int i = 0;
        while (i < 5) {
            es.submit(new CountDownLatchRunning(countDownLatch, i));
            i++;
        }

        countDownLatch.await();
        System.out.println("任务全部执行完毕!");

        es.shutdown();
    }
}

class CountDownLatchRunning implements Runnable {

    private int i = 0;
    private CountDownLatch countDownLatch;

    public CountDownLatchRunning(CountDownLatch countDownLatch, int i) {
        this.i = i;
        this.countDownLatch = countDownLatch;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(1000); // 任务执行了1秒
            if (i == 3) {
                throw new RuntimeException();
            }
            System.out.println(Thread.currentThread().getName() + ": 任务执行完毕!");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            countDownLatch.countDown();
        }
    }
}

main主线程会一致阻塞等待10条CountDownLatchRunning线程全部执行完成,当countDown()被调用10次之后,mian主线程继续执行

源码解析

CountDownLatch主要是两个方法:await()、countDown(),还有一个构造方法 CountDownLatch(int count)

构造方法:CountDownLatch(int count)

    public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        // 实例化一个Sync对象
        this.sync = new Sync(count);
    }

通过构造方法去设置AQS state的初始值为count,Sync是在CountDownLatch类中实现的AQS实现类

阻塞方法:await()

在例子中也有提到,await()会阻塞主线程,直到所有线程都countDown()数量等于count,也就是state==0

    public void await() throws InterruptedException {
        // 共享式获取AQS的同步状态
        sync.acquireSharedInterruptibly(1);
    }

调用的是AQS的acquireSharedInterruptibly方法:

	public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted()) // 线程中断 说明闭锁对线程中断敏感
            throw new InterruptedException();
        if (tryAcquireShared(arg) < 0) // 闭锁未使用完成 线程进入同步队列自旋等待 
            doAcquireSharedInterruptibly(arg);
    }

其中tryAcquireShared依赖于Sync的实现:

    /** 获取共享锁 */
    protected int tryAcquireShared(int acquires) {
        // AQS的同步状态为0则闭锁结束 可以进行下一步操作
        return (getState() == 0) ? 1 : -1;
    }

也就是需要当getState() == 0的时候,才可以进行继续执行,否则线程进入同步队列自旋等待(AQS同步队列的自旋等待)

计数方法:countDown()

调用countDown()方法会将计数器减1,直到计数器为0,代码如下:

public void countDown() {
        sync.releaseShared(1);
}

同样调用的是AQS的releaseShared方法:

	public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) { // 减少闭锁的计数器,只有计数器为0的时候才会返回true
            doReleaseShared(); // 唤醒被await方法阻塞的所有线程
           return true;
        }
        return false;
    }

这里的返回这对CountDownLatch没有用,其中tryReleaseShared方法依赖的是Sync的实现:

        /** 释放共享锁 */
        protected boolean tryReleaseShared(int releases) {
            // 死循环,如果CAS操作失败就会不断继续尝试
            for (;;) {
                int c = getState();
                if (c == 0) // 正常不会进入此逻辑
                    return false;
                int nextc = c-1; // 将计数器-1
                if (compareAndSetState(c, nextc)) // 更新计数器
                    // 如果操作成功,返回计数器是否为0,直接关系到是否执行doReleaseShared方法来唤醒后续线程
                    return nextc == 0;
            }
        }

可以看到,只有当计数器等于0的时候才会返回true,才会唤醒后续线程(调用await()自旋等待的线程)

带有超时等待的方法:await(long timeout, TimeUnit unit)

和await()类似,只是加入了超时检测,在自旋等待的过程中会去检查是否超时,超时则结束

总结

CountDownLatch类使用AQS同步器来实现计数。

  1. 在await()的时候,调用await()的线程将进入自旋等待,自旋过程会调用AQS的Sync实现的tryAcquireShared方法,只有当闭锁计数器等于0的时候,线程才能够继续执行
  2. 在其它线程调用countDown的方法时候,会将闭锁计数器减1(state-1),直至计数器等于0的时候,会唤醒后续线程获取闭锁(doReleaseShared),自旋等待中的线程才可以继续执行

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/17836.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!