标准 专业
多元 极客

JDK研究院(4)——CountDownLatch

我们在创建CountDownLatch时,通常会设置一个同步共享资源值:

public CountDownLatch(int count) {
    if (count < 0) throw new IllegalArgumentException("count < 0");
    this.sync = new Sync(count);
}

如果这个同步共享资源值<0,则直接抛出IllegalArgumentException异常。

而这个同步共享资源值用于创建同步控制器。

Sync

CountDownLatch中,内部同样实现了AbstractQueuedSynchronizer,资源获取与释放的方式是通过共享模式进行的。

我们继续看CountDownLatch#Sync内部类的方法。

构造方法

CountDownLatch内部实现的同步控制器在初始化时需要指定AbstractQueuedSynchronizer的同步资源大小:

Sync(int count) {
    setState(count);
}

获取同步资源数量

通过CountDownLatch#Sync#getCount()也可以随时获取这个同步资源数量:

int getCount() {
    return getState();
}

tryAcquireShared()

tryAcquireShared()用于尝试获取同步共享资源:

protected int tryAcquireShared(int acquires) {
    return (getState() == 0) ? 1 : -1;
}

如果当前的同步资源数量为0,则线程获取同步资源成功,否则获取失败,

为什么这么设计,请看原理分析部分。

tryReleaseShared()

tryReleaseShared()用于尝试释放同步共享资源:

protected boolean tryReleaseShared(int releases) {
    // 释放同步共享资源,如果释放后同步共享资源为0,则需要需要唤醒等待线程
    for (;;) {
        int c = getState();
        if (c == 0)
            return false;
        int nextc = c - 1;
        if (compareAndSetState(c, nextc))
            return nextc == 0;
    }
}
  1. 当前线程将会进入自旋状态
  2. 获取当前的同步共享资源数量:
    1. 如果当前的同步共享资源状态为0,证明已经释放所有同步共享资源,无需继续释放同步共享资源,同步队列中的等待线程也由其他线程唤醒,直接返回false。
    2. 计算释放后的剩余同步共享资源,并通过CAS操作更新最新的同步共享资源状态
    3. 释放同步共享资源后,需要判断此次释放是否需要唤醒同步队列中的等待线程。

释放同步共享资源时,与传进来的releases数量无关,即每次释放的同步共享资源默认为1。

核心方法

await()

在使用CountDownLatch时,我们使用await()等待同步共享资源状态归0,然后调用await()的线程再继续执行:

public void await() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}

acquireSharedInterruptibly()调用的是AbstractQueueSychronizer中的方法。

await(long timeout, TimeUnit unit)

我们可以是线程在指定时间内等待:

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

tryAcquireSharedNanos()调用的是AbstractQueueSychronizer中的方法。

countDown()

在构造CountDownLatch时设置的同步共享资源数量,需要调用countDown()方法进行递减:

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

public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        // 尝试唤醒后续等待的线程
        signalNext(head);
        return true;
    }
    return false;
}
  1. 首先尝试调用CountDownLatch#Sync#tryReleaseShared()方法释放资源:
    1. 释放成功,唤醒后继等待释放的节点,返回true。
    2. 如果tryReleaseShared()方法返回false,证明当前还有未释放的同步共享资源,同步队列中的线程还需要继续等待

原理分析

我们在看CountDownLatch原理之前,大都会了解到,使用CountDownLatch时,会有至少一个调用await()的线程等待其他线程countDown()同步共享资源直至全部释放后,继续执行。

那么整个流程是如何依靠AbstractQueuedSynchronizer进行的呢?

首先,我们初始化CountDownLatch时,设置了AbstractQueuedSynchronizer#state为我们设定的值。

接着,调用await()进行等待,await()会调用AbstractQueuedSynchronizer#acquireSharedInterruptibly()方法:

public final void acquireSharedInterruptibly(int arg)
    throws InterruptedException {
    // 如果线程被中断
    // 并且在尝试获取同步资源之后,入队等待获取不到同步资源,抛出中断异常
    // 没有抛出中断异常,证明成功获取了同步资源
    if (Thread.interrupted() ||
        (tryAcquireShared(arg) < 0 &&
         acquire(null, arg, true, true, false, 0L) < 0))
        throw new InterruptedException();
}

接着,会调用tryAcquireShared()方法,尝试获取共享资源,tryAcquireShared()即为CountDownLatch#Sync#tryAcquireShared()实现,从上文中的代码分析得知,在没有将同步共享资源countDown()到0时,都会返回-1,紧接着调用await()的线程会进入同步队列进行等待。

可以理解为,在同步队列中等待的线程,均是调用await()的线程

我们在调用countDown()时,会逐步递减AbstractQueuedSynchronizer#state的值。

AbstractQueuedSynchronizer#state0时,会通过AbstractQueuedSynchronizer#releaseShared()中的signalNext()唤醒同步队列中等待的线程,这样调用await()进行等待的线程就被唤醒了。

而事实上,它也没有对AbstractQueuedSynchronizer#state产生实质影响,也就是没有获得任何实质的同步共享资源。

赞(3) 投币

评论 抢沙发

慕勋的实验室慕勋的研究院

码字不容易,路过请投币

支付宝扫一扫

微信扫一扫