CountDownLatch使用及实现原理

CountDownLatch使用及实现原理

在前面的文章中我们了解了,AQSReentrantLock的实现原理。在本篇文章中我们介绍Java中的另一个同步工具CountDownLatch

CountDownLatch也是一个比较常用的同步工具,其也是在AQS的基础上实现的。

1 基本使用

CountDownLatch是也是Java中一个比较重要的同步工具,当我们希望主线程等待多条子线程逻辑处理完后再往下执行时我们可以使用这个工具类,其基本使用如下:

/**
 * Create By IntelliJ IDEA
 *
 * @author: XieHua
 * @date: 2021-06-07 20:33
 */
public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(5);
        for (int i = 0; i < 5; i ++) {
            Thread thread = new Thread(new MyRunnable(countDownLatch), "THREAD" + i);
            thread.start();
        }
        countDownLatch.await();
    }

    public static class MyRunnable implements Runnable {
        private CountDownLatch countDownLatch;

        public MyRunnable (CountDownLatch countDownLatch) {
            this.countDownLatch = countDownLatch;
        }

        public void run() {
            String threadName = Thread.currentThread().getName();
            System.out.println(threadName);
            countDownLatch.countDown();
        }
    }
}

上面这段代码中,我们创建了一个数量为5的CountDownLatch对象,然后创建五个子线程各自调用countDownLatch.countDown()方法,调用countDownLatch.await()方法阻塞主线程,运行程序我们发现时执行完五个子线程才会结束,运行结果如下:

image-20210607204616311

2 类结构

CountDownLatch的类结构是比较简单的,该类中有个Sync的内部类。Synck继承自AQSCountDownLatch的功是由该类实现的,其类图如下:

image-20210609121346423

3 实现原理

通过最开始的实例,我们知道CountDownLatch的主要方法有

  • 构造函数
  • countDown
  • await

接下来我们依次看下这三个方法的逻辑

3.1 构造函数

构造函数的源码如下:

public CountDownLatch(int count) {
    // count小于0抛出异常
    if (count < 0) throw new IllegalArgumentException("count < 0");
    // 创建Sync对象
    this.sync = new Sync(count);
}

Sync(int count) {
    // 设置state字段的值为count
    setState(count);
}

在构造方法中没什么逻辑,只是设置下state的值,通过该方法我们发现,在CountDownLatch中是使用AQS中的state字段记录需要等待的线程数。

3.2 await

这个方法是时主线程阻塞,其源码如下:

public void await() throws InterruptedException {
    // 调用AQS中的方法,在该方法中会调用tryAcquireShared方法
    sync.acquireSharedInterruptibly(1);
}

public final void acquireSharedInterruptibly(int arg)
    throws InterruptedException {
    // 线程被中断过抛出异常
    if (Thread.interrupted())
        throw new InterruptedException();
    // tryAcquireShared也是一个模板方法 需要使用的子类去实现
    if (tryAcquireShared(arg) < 0)
        doAcquireSharedInterruptibly(arg);
}

// CountDownLatch.Sync中的实现
protected int tryAcquireShared(int acquires) {
    // state = 0代表获取成功
    return (getState() == 0) ? 1 : -1;
}

通过上面的源码,我们发现CountDonwLatch中使用的是AQS的共享模式,由于在AQS源码介绍的那篇文章中我们没有说共享模式的代码,在这里就简单的点进去看一下,doAcquiresSharedInterruptibly的源码如下:

private void doAcquireSharedInterruptibly(int arg)
    throws InterruptedException {
    // 在阻塞队列中插入共享模式的节点
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        // 自旋
        for (;;) {
            final Node p = node.predecessor();
            if (p == head) {
                // 尝试获取共享锁
                int r = tryAcquireShared(arg);
                // 大于0代表获取成功
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
            }
            // 会将前置节点的状态值修改为 -1  并调用LockSupport.park方法阻塞线程
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

这段方法逻辑也是比较简单的,其过程如下:

1、尝试获取锁

2、获取锁失败,初始化阻塞队列并将线程以共享模式插入队列中

3、 自旋的模式修改前置节点状态为-1 并阻塞当前线程

3.3 countDown方法

这个方法用于减少count值,当值降低为0时代表释放锁成功,唤醒主线程,其源码如下:

public void countDown() {
    // 调用AQS中的释放共享锁的方法,在releaseShared方法中会调用tryReaseShared方法
    sync.releaseShared(1);
}

public final boolean releaseShared(int arg) {
    // 模板方法  需要在CountDownLatch中实现
    if (tryReleaseShared(arg)) {
       
        doReleaseShared();
        return true;
    }
    return false;
}

protected boolean tryReleaseShared(int releases) {
    // Decrement count; signal when transition to zero
    // 自旋
    for (;;) {
        // 当前的count
        int c = getState();
        if (c == 0)
            return false;
        int nextc = c-1;
        // CAS操作修改state的值
        if (compareAndSetState(c, nextc))
            // 修改后的值为0代表释放成功
            return nextc == 0;
    }
}

private void doReleaseShared() {
    // 自旋
    for (;;) {
        Node h = head;
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            if (ws == Node.SIGNAL) {
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            // loop to recheck cases
                // 唤醒下个节点  LockSupport.unpark
                unparkSuccessor(h);
            }
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        if (h == head)                   // loop if head changed
            break;
    }
}

4 总结

至此CountDownLatch的原理便介绍完成了,代码逻辑也是比较简单的,理解了AQS的处理逻辑,这些在其基础上实现的功能,都会很好理解。

CountDownLatch中使用AQS中的state来记录需要等待的线程数量,调用await方法将主线程添加进AQS的阻塞队列,调用countDown方法减少state的值,当state减少到0时再唤醒主线程。

如果感觉对您有帮助,欢迎关注我的公众号“Bug搬运小能手”,或者扫面下面二维码进行关注
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值