🖕 welcome to pay attention to my public number “Tong Elder brother read source code”, view more source code series articles, with Tong elder brother tour the ocean of source code.

(Mobile phone landscape view source more convenient)


The problem

(1) What is CountDownLatch?

(2) What features does CountDownLatch have?

(3) In what situations is CountDownLatch usually used?

(4) Can the initial number of CountDownLatch be adjusted?

Introduction to the

CountDownLatch, which can be translated as a countdown timer but seems to be inaccurate, allows one or more threads to wait for the other threads to complete their operations before performing any subsequent operations.

The common use of CountDownLatch is similar to thread.join (), waiting for the other threads to complete before performing the main task.

Class structure

CountDownLatch only contains Sync as an internal class, which has no fair/unfair mode, so it is a relatively simple synchronizer.

Another point to note here is that CountDownLatch does not implement the Serializable interface, so it is not Serializable.

Source code analysis

The inner class Sync

private static final class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = 4982264981922014374L;
    
    // Pass in the initial count
    Sync(int count) {
        setState(count);
    }
    // Get the number of times left
    int getCount(a) {
        return getState();
    }
    // Try to obtain the shared lock
    protected int tryAcquireShared(int acquires) {
        State = 0; count = 0; count = 0
        // state is not equal to 0, so count is not equal to 0
        return (getState() == 0)?1 : -1;
    }
    // Try to release the lock
    protected boolean tryReleaseShared(int releases) {
        for (;;) {
            / / the value of the state
            int c = getState();
            // If the value is equal to 0, it cannot be released again
            if (c == 0)
                return false;
            // Subtract count by 1
            int nextc = c-1;
            // Atom updates the value of state
            if (compareAndSetState(c, nextc))
                // Returns true when reduced to 0, which wakes up subsequent queued threads
                return nextc == 0; }}}Copy the code

Sync overrides the tryAcquireShared() and tryReleaseShared() methods and stores count in the state variable.

Note here that the arguments to the above two methods are not useful.

A constructor

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

The constructor needs to pass in a count, which is the initial count.

Await () method

// java.util.concurrent.CountDownLatch.await()
public void await(a) throws InterruptedException {
    / / call AQS acquireSharedInterruptibly () method
    sync.acquireSharedInterruptibly(1);
}
// java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly()
public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    // Try to get the lock, queue if it fails
    if (tryAcquireShared(arg) < 0)
        doAcquireSharedInterruptibly(arg);
}
Copy the code

The await() method is a method that waits for another thread to complete. It will try to acquire the shared lock and, if it fails, queue up in the AQS to be waked up.

TryAcquireShared () returns -1 if state is not 0, so all threads calling await() should queue if count is not 0.

CountDown () method

// java.util.concurrent.CountDownLatch.countDown()
public void countDown(a) {
    // call AQS to release the shared lock
    sync.releaseShared(1);
}
// java.util.concurrent.locks.AbstractQueuedSynchronizer.releaseShared()
public final boolean releaseShared(int arg) {
    // Try to release the shared lock. If successful, wake up the queued thread
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}
Copy the code

CountDown () method, a shared lock is released and the count is reduced by 1.

According to the Sync source code above, tryReleaseShared() increments count by one each time and returns true when it is zero to wake up the waiting thread.

Note that doReleaseShared() is the thread that wakes up the wait, which we analyzed in the previous section.

Use case

Here we simulate a usage scenario, we have a main thread and 5 helper threads, wait until the main thread is ready, 5 helper threads start to run, wait until the 5 helper threads finish running, the main thread continues to run, the general flow chart is as follows:

Let’s take a look at how this code should be written:

public class CountDownLatchTest {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch startSignal = new CountDownLatch(1);
        CountDownLatch doneSignal = new CountDownLatch(5);

        for (int i = 0; i < 5; i++) {
            new Thread(()->{
                try {
                    System.out.println("Aid thread is waiting for starting.");
                    startSignal.await();
                    // do sth
                    System.out.println("Aid thread is doing something.");
                    doneSignal.countDown();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }

        // main thread do sth
        Thread.sleep(2000);
        System.out.println("Main thread is doing something.");
        startSignal.countDown();

        // main thread do sth else
        System.out.println("Main thread is waiting for aid threads finishing.");
        doneSignal.await();

        System.out.println("Main thread is doing something after all threads have finished."); }}Copy the code

This code is split into two sections:

In the first stage, the five worker threads wait for the startSignal, which is sent by the main thread, so the five worker threads call startsignal.await () to wait for the startSignal, and when the main thread is done, call startsignal.countdown () to tell the worker thread to start.

Second, the main thread waits for the signal completed by 5 helper threads. The signal is issued by 5 helper threads, so the main thread calls the donesignal.await () method to wait for the signal completed, and the 5 helper threads call the donesignal.countdown () method to send their signal completed when they finish their work. When the completion signal reaches 5, the main thread is awakened to continue the subsequent logic.

conclusion

CountDownLatch allows one or more threads to wait for the other threads to complete their operations before performing subsequent operations.

(2) CountDownLatch is realized by AQS shared lock mechanism;

(3) Count is passed during the initialization of CountDownLatch;

CountDown () count is reduced by 1 per call;

(5) Every time we call await() method, we will try to obtain lock. In this case, we will actually check whether the state variable of AQS is 0;

(6) Wake up queued threads (which call await() to queue) when count (state) is reduced to 0;

eggs

(1) Can the initial number of CountDownLatch be adjusted?

A: When we learned Semaphore, the number of licenses can be adjusted at any time. Can the initial number of CountDownLatch be adjusted at any time? The answer is no, it provides no way to modify (increase or decrease) the number of times, except by using reflex cheating.

(2) Why does CountDownLatch use shared locks?

A: When analyzing ReentrantReadWriteLock, we learned the SHARED lock mode of AQS. For example, if the current lock is acquired as a mutex by one thread, all threads that need to obtain the shared lock are queued in the AQS queue. When the mutex is released, One by one, the threads queuing to acquire the shared lock are woken up. Note that the term is “wake up one by one”, meaning that the threads waiting to acquire the shared lock are not woken up at once.

At this point, is it clear? Because the await() of CountDownLatch can be called multiple times by multiple threads, the threads will queue up in the AQS queue when the count count is reduced to zero, and they will need to be woken up to continue the task. This is not possible with a mutex, which is mutually exclusive between threads. Only one can be waked at a time and there is no guarantee that all the threads waiting with the await() method will be waked when count decreases to 0.

(3) How does CountDownLatch differ from Thread.join()?

Unlike Thread.join(), which is called within the main Thread and cannot be notified until the invoked Thread has finished, CountDownLatch’s CountDownLatch () method can be called at any point in the Thread’s execution, giving it more flexibility.

Recommended reading

The beginning of the Java Synchronization series

2, Unbroadening Java magic class parsing

JMM (Java Memory Model)

Volatile parsing of the Java Synchronization series

Synchronized parsing of Java series

6, Deadknock Java synchronization series write a Lock Lock yourself

7. AQS of The Java Synchronization series

ReentrantLock (a) — fair lock, unfair lock

ReentrantLock – Conditional lock

ReentrantLock VS Synchronized Java series

ReentrantReadWriteLock source code parsing

Semaphore source code analysis of Semaphore Java synchronization series

Welcome to pay attention to my public number “Tong Elder brother read source code”, view more source code series articles, with Tong elder brother tour the ocean of source code.