preface

This article begins with an analysis of CountDownLatch’s source code. After the analysis, an example will be used to demonstrate CountDownLatch’s application scenario.

1, the introduction of

CountDownLatch is essentially a shared lock that maintains Sync internally. Sync inherits from the AQS abstract class. Sync is neither fair nor unfair, and allows one or more threads to wait for other threads to complete their operations before performing subsequent operations.

2. Analyze the source code

2.1 list of functions

// Construct a CountDownLatch that internally maintains the given counter
CountDownLatch(int count)
// Wait until CountDownLatch's internal counter is 0
void await(a)
// Wait for CountDownLatch's internal counter to be 0 for the specified time
boolean await(long timeout, TimeUnit unit)
// CountDownLatch's internal counter decreases by 1
void countDown(a)
// Returns the current count.
long getCount(a)
String toString(a)
Copy the code

2.2. Analyze await method

This is the await method in CountDownLatch

public void await(a) throws InterruptedException {
	sync.acquireSharedInterruptibly(1);
}
Copy the code

AcquireSharedInterruptibly method implementation in AQS, source code is as follows:

public final void acquireSharedInterruptibly(int arg)
    throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    // Attempts to obtain the shared lock
    if (tryAcquireShared(arg) < 0)
    	// If it fails, the AQS queue will queue up and wait to wake up
        doAcquireSharedInterruptibly(arg);
}
Copy the code

TryAcquireShared () ¶ tryAcquireShared () ¶

protected int tryAcquireShared(int acquires) {
	// If state is 0, the shared lock is acquired successfully, otherwise the shared lock is queued
	return (getState() == 0)?1 : -1;
}
Copy the code

DoAcquireSharedInterruptibly method is the method of AQS Shared lock queue to retry, ReentrantReadWriteLock and Semaphore has talked about before, not repeat.

2.3. Analyze countDown method

This is the countDown method in CountDownLatch

public void countDown(a) {
	sync.releaseShared(1);
}
Copy the code

ReleaseShared method in AQS, source code as follows:

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

The tryReleaseShared method is in the CountDownLatch internal class Sync, and the source code is as follows:

protected boolean tryReleaseShared(int releases) {
    // Decrement count; signal when transition to zero
     for (;;) {
     	 // Get the counter value
         int c = getState();
         // returns 0
         if (c == 0)
             return false;
         / / minus 1
         int nextc = c-1;
         //CAS updates the value and returns true only when the counter value changes from 1 to 0
         if (compareAndSetState(c, nextc))
             return nextc == 0; }}Copy the code

The doReleaseShared method is implemented in AQS to wake up queued threads in CLH queue in turn. This method has been analyzed before and will not be repeated.

3, sample

The business logic of the following code, such as the order business:

  1. Main thread ready
  2. Three threads start processing business
  3. The main thread continues after the business logic is processed
public class CountDownLatchTest {

    public static final CountDownLatch WORK_THREAD = new CountDownLatch(3);
    public static final CountDownLatch MAIN_THREAD = new CountDownLatch(1);

    public static void main(String[] args) throws Exception{
        for (int i=0; i<3; i++){
            new Thread(() -> { method(); }).start();
        }
        // Main thread logic
        Thread.sleep(2000);
        System.out.println("The main thread is ready...");
        MAIN_THREAD.countDown();
        WORK_THREAD.await();
        System.out.println("The main thread continues after the service is processed...");
    }

    public static void method(a){
        System.out.println(The thread name for waiting is: + Thread.currentThread().getName());
        try {
            // Handle business
            MAIN_THREAD.await();
            Thread.sleep(100);
            System.out.println("The thread name of finish business is:" + Thread.currentThread().getName());
            WORK_THREAD.countDown();
        } catch(InterruptedException e) { e.printStackTrace(); }}}Copy the code

Output result:

The Thread name for waiting is Thread-2The Thread name for waiting is Thread-1The Thread name for waiting is Thread-0The main thread is ready... The Thread name of the Finish business is: Thread-1The Thread name of the Finish business is: Thread-2The Thread name of the Finish business is: Thread-0After services are processed, the main thread continues......Copy the code

conclusion

In the above example, the wait() for CountDownLatch maintained by the main thread is called three times and countDown() once, and after the countDown() method reduces state to 0, all threads blocked by the previous three wait() methods are awakened to continue executing.

Those of you who have read my previous analysis of ReentrantReadWriteLock and Semaphore should understand better why CountDownLatch uses shared locks rather than mutex locks.

The release of a shared lock is notified one by one of the queued threads in the queue, while the release of a mutex is notified one == at a time.

If you found this post helpful, please give it a thumbs up and a follow.