In previous articles, we learned how AQS and ReentrantLock are implemented. In this article we introduce CountDownLatch, another synchronization tool in Java.

CountDownLatch is also a common synchronization tool, which is also based on AQS.

1 Basic Usage

CountDownLatch CountDownLatch is an important synchronization tool in Java. It is used when we want the main thread to wait for the logic of multiple sliver threads to complete. The basic use of this tool is as follows:

/**
 * Create By IntelliJ IDEA
 *
 * @author: XieHua
 * @date: 2021-06-07 * / to him
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(a) { String threadName = Thread.currentThread().getName(); System.out.println(threadName); countDownLatch.countDown(); }}}Copy the code

In this code, we create a CountDownLatch object of 5, and then block the main thread by each of the five child threads calling countdownlatch.countdown () and countdownlatch.await (). When we run the program, we find that the execution of five child threads will end, and the result is as follows:

2 class structure

The CountDownLatch class structure is relatively simple, with an inner class of Sync. Synck inherits from AQS, and CountDownLatch is implemented by this class. Its class diagram is as follows:

3 Implementation Principle

From our initial example, we know that the main methods for CountDownLatch are

  • The constructor
  • countDown
  • await

Let’s take a look at the logic of each of these methods

3.1 Constructors

The constructor source is as follows:

public CountDownLatch(int count) {
    // Count < 0 throws an exception
    if (count < 0) throw new IllegalArgumentException("count < 0");
    // Create Sync object
    this.sync = new Sync(count);
}

Sync(int count) {
    // Set the state field to count
    setState(count);
}
Copy the code

There is no logic in the constructor, just setting the value of state. In this method, we find that the state field in the AQS is used in CountDownLatch to record the number of threads waiting.

3.2 await

This method blocks the main thread, and its source is as follows:

public void await(a) throws InterruptedException {
    // Call the method in AQS, where the tryAcquireShared method is called
    sync.acquireSharedInterruptibly(1);
}

public final void acquireSharedInterruptibly(int arg)
    throws InterruptedException {
    // The thread was interrupted and raised an exception
    if (Thread.interrupted())
        throw new InterruptedException();
    // tryAcquireShared is also a template method that needs to be implemented using a subclass
    if (tryAcquireShared(arg) < 0)
        doAcquireSharedInterruptibly(arg);
}

// the implementation of countdownlatch.sync
protected int tryAcquireShared(int acquires) {
    // state = 0 indicates success
    return (getState() == 0)?1 : -1;
}
Copy the code

Through the above source code, we found that CountDonwLatch AQS is used in sharing mode, because in AQS source is introduced in the article we didn’t say sharing pattern code, he simple point in to look at here, doAcquiresSharedInterruptibly source code is as follows:

private void doAcquireSharedInterruptibly(int arg)
    throws InterruptedException {
    // Insert nodes in shared mode into the blocking queue
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        / / spin
        for (;;) {
            final Node p = node.predecessor();
            if (p == head) {
                // Try to obtain the shared lock
                int r = tryAcquireShared(arg);
                // The value greater than 0 is successful
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    failed = false;
                    return; }}// Changes the state value of the front node to -1 and blocks the thread by calling the locksupport. park method
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                throw newInterruptedException(); }}finally {
        if(failed) cancelAcquire(node); }}Copy the code

This section of method logic is also relatively simple, its process is as follows:

1. Try to acquire the lock

2. Failed to acquire the lock, initialize the blocking queue and insert the thread into the queue in shared mode

3. Spin mode changes the leading node state to -1 and blocks the current thread

3.3 countDown method

This method is used to reduce the count value, when the value is reduced to 0, the lock is released successfully, wake up the main thread, the source code is as follows:

public void countDown(a) {
    // Call the AQS method to release the shared lock. In releaseShared the tryReaseShared method is called
    sync.releaseShared(1);
}

public final boolean releaseShared(int arg) {
    // Template methods need to be implemented in CountDownLatch
    if (tryReleaseShared(arg)) {
       
        doReleaseShared();
        return true;
    }
    return false;
}

protected boolean tryReleaseShared(int releases) {
    // Decrement count; signal when transition to zero
    / / spin
    for (;;) {
        // Current count
        int c = getState();
        if (c == 0)
            return false;
        int nextc = c-1;
        The CAS operation modifies the value of state
        if (compareAndSetState(c, nextc))
            // If the value is 0, the release is successful
            return nextc == 0; }}private void doReleaseShared(a) {
    / / spin
    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
                // Wake up the next node 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; }}Copy the code

4 summarizes

So far, the principle of CountDownLatch has been introduced and the code logic is relatively simple. After understanding the processing logic of AQS, the functions realized on the basis of this latch will be easily understood.

Use the state in the AQS in CountDownLatch to record the number of threads to wait, call the await method to add the main thread to the AQS blocking queue, call the countDown method to reduce the value of state, and wake up the main thread when the state drops to 0.

If you feel helpful, welcome to pay attention to the public number, your attention is my biggest motivation to update ~