[Java concurrency] - 5. Source code analysis of the concurrency tool class CountDownLatch

Article catalog

1. Introduction

CountDownLatch allows one or more threads to wait for other threads to complete the operation.

Similar to the implementation of thread's join method, but join is used to let the current executing thread wait for the execution of the join thread to finish. The implementation principle is to constantly check whether the join thread survives. If the join thread survives, the current thread will wait forever.

Unlike join, CountDownLatch allows developers to define where threads execute. CountDownLatch's constructor takes an int type parameter as a counter. If you want to wait for n points to complete, pass in n here. When we call the countDown method of CountDownLatch, n will be reduced by 1, and the wait method of CountDownLatch will block the current thread until n becomes zero. Since the countDown method can be used anywhere, the n points mentioned here can be n threads or N execution steps in a thread. When it is used in multiple threads, you only need to pass the reference of CountDownLatch to the thread.

Therefore, CountDownLatch is more flexible than join.

Note that the counter must be greater than or equal to 0. Only when it is equal to 0, the counter is zero. When the await method is called, it will not
Blocks the current thread. CountDownLatch is not possible to reinitialize or modify the
The value of the internal counter. One thread calls the countDown method, happen before, and the other calls the await method.

2. How to use CountDownLatch

public class CountDownLatchDemo {

    public static void main(String[] args) {
        CountDownLatch countDownLatch = new CountDownLatch(3);

        IntStream.range(0,3).forEach(i -> new Thread(() -> {
            try {
                Thread.sleep(2000);
                System.out.println("hello");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                countDownLatch.countDown();
            }
        }).start());

        System.out.println("Sub thread execution completed");

        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Main thread execution completed");
    }
}

Results of execution

Sub thread execution completed
hello
hello
hello
 Main thread execution completed

As you can see, we put Calculator 3 in CountDownLatch, and the await method is executed when all three threads are finished. In this way, if you encounter a precondition for a thread to execute and several other threads must execute to a certain step, you can't use the join method. At this time, you can use CountDownLatch to realize the thread waiting.

However, there is a big problem in using CountDownLatch above. If the calculator and execution CountDownLatch.countDown The inconsistent number of methods in () will cause the calculator to fail to reach 0, which makes the await loop. In order to solve this problem, a variant of the await method, await(long timeout, TimeUnit unit), has set the waiting time. If the waiting time calculator is not 0, the await thread will wake up automatically to prevent the thread from dead cycle

public class CountDownLatchDemo {

    public static void main(String[] args) {
        CountDownLatch countDownLatch = new CountDownLatch(3);

        IntStream.range(0,3).forEach(i -> new Thread(() -> {
            try {
                System.out.println("thread" + i);
                Thread.sleep(2000);
                System.out.println("hello");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                countDownLatch.countDown();
            }
        }).start());

        System.out.println("Sub thread execution completed");

        try {
            countDownLatch.await(1000,TimeUnit.MICROSECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Main thread execution completed");
    }
}

Program execution results

Sub thread execution completed
thread0
thread1
thread2
 Main thread execution completed
hello
hello
hello

3. Analysis of methods in CountDownLatch class

CountDownLatch contains the following methods

Here are some important methods to analyze.

Construction method

Analysis shows that CountDownLatch has only one construction method with parameters

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

In the construction method,

  • The first step is to verify the passed in value. (because this is a minus calculator, the count must be greater than 0.)
  • 2: A private class Sync of CountDownLatch class is declared
 private static final class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = 4982264981922014374L;

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

It is known that the Sync class is an AQS implementation class. So the bottom layer of CountDownLatch is based on AQS.

    protected final void setState(int newState) {
        state = newState;
    }

Its final execution to the setState method sets the value of the counter.

await method

public void await() throws InterruptedException {
//The method in the abstract class (exactly the method inherited by the sync class but not overridden) is executed
//As for the parameter 1 passed in, it has no practical significance here. It is formally specified in AQS
   sync.acquireSharedInterruptibly(1);
}

//Methods of abstract class AbstractQueuedSynchronizer
public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        //Determine whether the thread is interrupted
        if (Thread.interrupted())
            throw new InterruptedException();
        //
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
    }
//Methods of abstract class AbstractQueuedSynchronizer
//This method has no specific implementation, so you should find the overridden method in its implementation class
protected int tryAcquireShared(int arg) {
        throw new UnsupportedOperationException();
    }
//CountDownLatch internal class Sync method
protected int tryAcquireShared(int acquires) {
//This is to determine whether the counter passed in by the CountDownLatch constructor is 0. The parameter is not used.
//If the negative acquiresharedinterruptible method is returned, it will enter doacquiresharedinterruptible
//Wait for the value of the counter to change to 0, and if a positive number is returned, acquiresharedinterruptible is executed by
//That is to say, the counter value in CountDownLatch is 0, which can wake up the main thread to execute
            return (getState() == 0) ? 1 : -1;
        }

The await method will wait until the counter in CountDownLatch is not 0. The thread executing the await method will not wake up until the counter is 0. Of course, the thread may also be woken up by hitting, and then an InterruptedException exception will be thrown.

So the logic of the await method is very simple, that is, to judge that the counter value algorithm in CountDownLatch is 0,

  • Put the thread into the waiting queue if it is not 0
  • 0, the thread continues and executes

await(long timeout, TimeUnit unit)

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

public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        return tryAcquireShared(arg) >= 0 ||
            doAcquireSharedNanos(arg, nanosTimeout);
    }

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

Similar to the logic of the await method, only in the * doAcquireSharedNanos(arg, nanosTimeout); * method, the timeout wake-up is processed. This method is realized by AQS, so when analyzing AQS code, we should analyze it concretely.

That is, if the timeout time is reached, the counter in CountDowmLatch has not been set to 0, and the await method will wake up as usual

countDown() method

public void countDown() {
        sync.releaseShared(1);
    }
//Methods of abstract class AbstractQueuedSynchronizer
public final boolean releaseShared(int arg) {
//Judge whether tryrereleaseshared returns, if it is true, wake up the thread waiting through dorreleaseshared and return true
//If false is returned for false, nothing will be done
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }
//CountDownLatch internal class Sync method
protected boolean tryReleaseShared(int releases) {
            // Decrement count; signal when transition to zero
            for (;;) {
                int c = getState();
                //Judge whether the counter (State) is 0. Because if the counter is 0, no wait operation will be performed
                //Return directly. If it is 0, there must be a thread executing before
                //The tryreleased method successfully decrements the counter by one, so it returns false
                if (c == 0)
                    return false;
                //Reduce the value of c by one through CAS, and then judge whether the counter is 0. If it is 0, return true and wake up
                //So wait for the thread. Responsible for returning false without any processing
                int nextc = c-1;
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }

The function of this method is to decrement the counter by one. If the counter value changes to 0, all waiting threads will wake up.

After this method is executed, the value of the counter in CountDownLatch can only be > = 0 in the end.

Other methods in the CountDownLatch class have no analysis value, so no analysis is done here.

Keywords: calculator

Added by jmcc on Sat, 20 Jun 2020 07:43:09 +0300