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.