Depth analysis of the principle of thread synchronization tool CountDownLatch

Role of 0 CountDownLatch

CountDownLatch as a synchronization tool between a multi-threaded, which allows one or more threads wait for other threads (may be more) after the completion of the work, and then resume execution.

Like this:

CountDownLatch role

Speaking from a 1 Demo

We take Demo source code is given directly look at the source code of this demo can be seen as a simulation racing scene. Race certainly run fast athletes have slow Runners, each athlete will represent a thread. After the end of the athletes heard gunshots began to start, but after the last athlete to reach the terminal, marking the game. The whole process is shown below:

CountDownLatch running simulation

Source follows

public class Race {

    private static final int N = 4;

    public static void main(String[] args) throws InterruptedException {
        CountDownLatch startSignal = new CountDownLatch(1);  // 鸣枪开始信号
        CountDownLatch doneSignal = new CountDownLatch(N);   // 等待N个运动员都跑完后,比赛结束(结束信号)

        for (int i = 0; i < N; ++i) // N个运动员准备就绪,等待枪声
            new Thread(new Runner(startSignal, doneSignal, i)).start();

        Thread.sleep(1000); // 等待所有运动员就绪
        System.out.println("所有运动员就绪");
        startSignal.countDown();      // 鸣枪,开赛
        System.out.println("比赛进行中...");
        doneSignal.await();           // 等待N个运动员全部跑完(等待doneSignal变为0)
        System.out.println("比赛结束");
    }
}

class Runner implements Runnable {
    private final CountDownLatch startSignal;
    private final CountDownLatch doneSignal;
    private int number;

    Runner(CountDownLatch startSignal, CountDownLatch doneSignal, int number) {
        this.startSignal = startSignal;
        this.doneSignal = doneSignal;
        this.number = number;
    }
    public void run() {
        try {
            // 等待枪声(等待开始信号startSignal变为0)
            System.out.println(number + "号运动员准备就绪");
            startSignal.await();
            // 赛跑
            System.out.println(number + "号运动员跑步中...");
            Thread.sleep(new Random().nextInt(10) * 1000);
            // 此运动员跑到终点
            System.out.println(number + "号运动员到达终点");
            doneSignal.countDown();
        } catch (InterruptedException ex) {} // return;
    }
}
复制代码

After running the above code, the following output:

0号运动员准备就绪
3号运动员准备就绪
2号运动员准备就绪
1号运动员准备就绪
所有运动员就绪
比赛进行中...
0号运动员跑步中...
1号运动员跑步中...
2号运动员跑步中...
3号运动员跑步中...
2号运动员到达终点
1号运动员到达终点
0号运动员到达终点
3号运动员到达终点
比赛结束
复制代码

Now, deep into the details of the code, look at how CountDownLatch initialization, countDown method, await method is implemented.

Class 2 CountDownLatch FIG.

By following diagram to find out the class inheritance relationship CountDownLatch

FIG class CountDownLatch

3 CountDownLatch initialization

CountDownLatch only a constructor:

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

He will initiate a Sync, which is one of his inner class, similar to ReentrantLock, Sync also inherited AbstractQueuedSynchronizer (AQS).

AQS is a what? Can refer to the author of another article: the Java queue synchronizer (AQS) in the end is how one thing

Then look at the source code Sync

private static final class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = 4982264981922014374L;

    // 调用AQS的setState方法,将state赋值为count的值
    Sync(int count) {
        setState(count);
    }

    // 获取AQS state的当前值
    int getCount() {
        return getState();
    }

    protected int tryAcquireShared(int acquires) {
        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;
            int nextc = c-1;
            if (compareAndSetState(c, nextc))
                return nextc == 0;
        }
    }
}
复制代码

So CountDownLatch initialization, in fact, is to assign a value to the parameter count state AQS, it is still controlled by state synchronization status.

4 await implemented method

Still racing with the example above to illustrate the problem. Here we consider only the scene of gunfire wait for all athletes.

Recall the example of race, create a firing signal in the following way:

CountDownLatch startSignal = new CountDownLatch(1);  // 鸣枪开始信号
复制代码

Then create the N threads (N represents the athletes), and call its start method allowed to begin execution (athlete is ready, waiting for the shots to start running).

Then by calling startSignal.await in the run method (), waiting for shots to achieve the action (in fact, such as the value of startSignal reduced to 0).

We look at how he's await.

public void await() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}
复制代码

Call acquireSharedInterruptibly method of AQS

public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
    // 判断线程是否已经被中断
    if (Thread.interrupted())
        throw new InterruptedException();
    // 调用CountDownLatch.Sync的tryAcquireShared方法
    // 此方法判断count的值是否==0,如果==0,返回1,否则返回-1
    // 目前我们还没有执行countDown,所以count肯定不等于0,这里肯定返回-1
    // 所以会执行到AQS的doAcquireSharedInterruptibly方法中
    if (tryAcquireShared(arg) < 0)
        doAcquireSharedInterruptibly(arg);
}

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

AQS.doAcquireSharedInterruptibly implementation as follows

/**
 * Acquires in shared interruptible mode.
 * @param arg the acquire argument
 */
// 此方法会在count>0时将当前线程加入到等待队列中
// 由于我们目前还没有执行countDown,所以count会保持>0
// 启动的N个线程会全部加入到队列中
private void doAcquireSharedInterruptibly(int arg)
    throws InterruptedException {
    // 将当前线程添加到等待队列中(SHARED模式)
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        // 自旋获取同步状态
        for (;;) {
            final Node p = node.predecessor();
            if (p == head) {
                // 依然调用CountDownLatch.Sync的tryAcquireShared方法判断
                // 如果count降为0,退出自旋
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
            }
            // 将node的waitStatus设置为-1(常量SIGNAL,表示需要唤醒),并阻塞
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
复制代码

Suppose N = 4, then all four threads after start, will be added to all the spin-wait queue, as follows:

CountDownLatch.await spin-wait

5 countDown method of realization

countDown method is actually the state of the AQS value -1. Then determine whether the current value of the state == 0, if equal to 0, indicating that all threads are executed over, need to wake up all waiting threads.

Continuing the above scenario is still, after firing, all the athletes start running.

This action shots actually executed in the main thread:

startSignal.countDown();
复制代码

This is equivalent to just send all the threads in the queue to resume execution of a signal, all the threads will wake up, continue to await behind the code.

Specific countDown did Shane?

public void countDown() {
    sync.releaseShared(1);
}
复制代码

He will call releaseShared method of AQS

public final boolean releaseShared(int arg) {
    // 调用CountDownLatch.Sync的tryReleaseShared方法
    // 该方法尝试将count值-1,并判断-1后的count是否==0,如果==0,返回true,否则false
    // 该方法的源码已经在Sync的源码中给出,可翻阅上文查看
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}
复制代码

Since the initial value of count == 1 startSignal after startSignal.countDown (), count becomes zero. So tryReleaseShared returns true.

Then started doReleaseShared, queue thread wakes up.

doReleaseShared method of AQS.

/**
 * Release action for shared mode -- signals successor and ensures
 * propagation. (Note: For exclusive mode, release just amounts
 * to calling unparkSuccessor of head if it needs signal.)
 */
private void doReleaseShared() {
    /*
     * Ensure that a release propagates, even if there are other
     * in-progress acquires/releases.  This proceeds in the usual
     * way of trying to unparkSuccessor of head if it needs
     * signal. But if it does not, status is set to PROPAGATE to
     * ensure that upon release, propagation continues.
     * Additionally, we must loop in case a new node is added
     * while we are doing this. Also, unlike other uses of
     * unparkSuccessor, we need to know if CAS to reset status
     * fails, if so rechecking.
     */
    for (;;) {
        Node h = head;
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            // Node.SIGNAL == -1
            // 由上文可知,进入队列的线程的waitStatus都等于-1
            // 所以这里为true
            if (ws == Node.SIGNAL) {
                // 尝试将waitStatus从-1改为0,如果修改成功,就恢复这个线程的执行状态
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            // loop to recheck cases
                unparkSuccessor(h);
            }
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        if (h == head)                   // loop if head changed
            break;
    }
}
复制代码

Here, the blocked thread and resume execution, where the recovery of it? Spin is just waiting there.

The above code directly to win, and then explain (comments section)

private void doAcquireSharedInterruptibly(int arg)
    throws InterruptedException {
    // 将当前线程添加到等待队列中(SHARED模式)
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        // 线程被释放后,继续下一次循环
        for (;;) {
            final Node p = node.predecessor();
            if (p == head) {
                int r = tryAcquireShared(arg);
                // 获取头节点,从头结点开始释放,由于count已经降为0,所以r >= 0为true
                // 然后会将自己摘除当前队列,使下一个节点成为头节点
                // 等下一个节点也恢复过来后,同样执行上面的过程
                // 这样,队列中的所有线程就被释放了
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
            }
            // 将node的waitStatus设置为-1(常量SIGNAL,表示需要唤醒),并阻塞
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
复制代码

Summary 6

This paper details how the source code level CountDownLatch works. CountDownLatch also based AQS achieve, so understanding the mechanism of AQS, is essential for understanding this article. In fact, CountDownLatch the core is controlled by the state of AQS, to synchronize state between multiple threads.

Guess you like

Origin juejin.im/post/5d14b8cae51d4577523f23be