CountDownLatch demo and source code

Posted by Spikey on Thu, 14 May 2020 07:40:22 +0200

In the previous project, we encountered a complex query, that is, we need to query 20 pieces of data in pages first, and then divide these 20 pieces of data into four categories according to the event type, and query the unique information of these four categories with threads respectively, and then after all the threads have finished executing, we will sort these 20 pieces of data according to the event, and finally return them to the front end. Because of the thread query used, I don't know when it will be finished. After a long time, I found a solution, which is to use CountDownLatch.

CountDownLatch and CyclicBarrier are multithreaded utility classes under the java.util.concurrent package. Today I'll just talk about CountDownLatch. Next time I'll see CyclicBarrier.

1, CountDownLatch

1. Function of countdownlatch:

CountDownLatch is a counter lock, through which you can complete a function similar to blocking the current thread, that is, one or more threads wait until the operation performed by other threads is completed.

2.CountDownLatch application scenario: a task is divided into multiple tasks for execution.

Scenario 1:

Take the example above. Threads 1, 2, 3 and 4 are blocked when they are in the fence position. You need to wait for all threads to execute before you can open the fence and start the sorting method.

package cn.seven.countdownlatch;

import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * ClassName:    Demo1
 * Package:    cn.seven.countdownlatch
 * Description: CountdownLatchTest01
 * Datetime:    2020/5/13   20:47
 *
 * @Author: charon
 */
public class Demo1 {

    /**
     * @param args parameter
     */
    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(4);
        final CountDownLatch latch = new CountDownLatch(4);

        System.out.println("Main thread,"+Thread.currentThread().getName()+"Execute here,Execute in 4 threads");

        Runnable runnable0 = () -> {
            try {
                System.out.println("Child thread"+Thread.currentThread().getName()+"Start execution");
                Thread.sleep(10000);
                System.out.println("Child thread"+Thread.currentThread().getName()+"end of execution");
                // If the current thread calls this method, the count is decremented by one
                latch.countDown();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
        executorService.execute(runnable0);

        Runnable runnable1 = () -> {
            try {
                System.out.println("Child thread"+Thread.currentThread().getName()+"Start execution");
                Thread.sleep(11000);
                System.out.println("Child thread"+Thread.currentThread().getName()+"end of execution");
                // If the current thread calls this method, the count is decremented by one
                latch.countDown();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
        executorService.execute(runnable1);

        Runnable runnable2 = () -> {
            try {
                System.out.println("Child thread"+Thread.currentThread().getName()+"Start execution");
                Thread.sleep(12000);
                System.out.println("Child thread"+Thread.currentThread().getName()+"end of execution");
                // If the current thread calls this method, the count is decremented by one
                latch.countDown();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
        executorService.execute(runnable2);

        Runnable runnable3 = () -> {
            try {
                System.out.println("Child thread"+Thread.currentThread().getName()+"Start execution");
                Thread.sleep(13000);
                System.out.println("Child thread"+Thread.currentThread().getName()+"end of execution");
                // If the current thread calls this method, the count is decremented by one
                latch.countDown();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
        executorService.execute(runnable3);

        System.out.println("Main thread"+Thread.currentThread().getName()+"Wait for subthread execution to complete");
        //Blocks the current thread until the counter value is 0
        latch.await();
        System.out.println("Main thread"+Thread.currentThread().getName()+"Start sorting...");
    }
}

Scenario 2:

We've all seen running competitions, where the athletes wait for the referee to make a shot, and then the athletes start running, and when all the distance mobilization is finished, the referee will count the ranking.

package cn.seven.countdownlatch;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * ClassName:    Demo2
 * Package:    cn.seven.countdownlatch
 * Description: Simulate the athlete competition, start the shooter to start, wait for all the athletes to finish running, count the ranking
 * Datetime:    2020/5/13   21:14
 *
 * @Author: charon
 */
public class Demo2 {

    /**
     * implement
     * @param args
     */
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        //The boom fence
        final CountDownLatch countDownLatch1 = new CountDownLatch(1);
        //The fence at the end of the game
        final CountDownLatch countDownLatch2 = new CountDownLatch(3);

        for (int i = 0;i< 3;i++){
            Runnable runnable = () -> {
                try {
                    System.out.println("Athletes"+Thread.currentThread().getName()+"Waiting signal gun");
                    // Block the thread before running. Wait until count of countDownLatch1 is 0
                    countDownLatch1.await();
                    System.out.println("Athletes"+Thread.currentThread().getName()+"Run");
                    Thread.sleep(10);
                    System.out.println("Athletes"+Thread.currentThread().getName()+"To the end");

                    countDownLatch2.countDown();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            };
            executorService.execute(runnable);
        }
        try {
            Thread.sleep(5000);

            System.out.println("referee"+Thread.currentThread().getName()+"About to fire a signal gun");
            //Recursively subtract one until count is 0
            countDownLatch1.countDown();
            System.out.println("referee"+Thread.currentThread().getName()+"Ring the signal gun and wait for the athletes to finish running");
            //Wait for count of countDownLatch2 to be reduced to 0 before proceeding with the following code
            countDownLatch2.await();
            System.out.println("The athletes are at the end, referee"+Thread.currentThread().getName()+"Statistical ranking");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        executorService.shutdown();
    }

}

3. Let's analyze two important methods of CountDownLatch!!

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

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

This is the constructor of CountDownLatch. You need to set an initial size, that is, the number of threads. If count is less than 0, an exception is thrown directly. Otherwise, count in the constructor is passed to the state of AQS.

So countDown() in CountDownLatch is the change of state state. await() is to determine whether all tasks are completed by polling the state status.

countDown source code analysis:

After the current thread calls the method, the counter value will be decremented. If the counter is 0 after decrement, all threads that call the await method and are blocked will wake up. Otherwise, nothing will be done.

public void countDown() {
     sync.releaseShared(1);//The technology of decreasing lock. If the count is 0, the lock will be released. If the count is greater than 0, the count will be reduced by one
}

public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {  //Try to release the lock. This method is overridden in sync. If the value of count is 0, do the following
        doReleaseShared();  
        return true;
    }
    return false;
}

/**
  * CountDownLatch This method of trying to release the lock is overridden by the internal class sync of
  */
protected boolean tryReleaseShared(int releases) {
    //  Decrement count; a signal when converted to zero
    for (;;) { //Use the dead loop to try to release the lock. The current thread successfully completes cas to reduce the count value (state value) and update it to state
        int c = getState();
        if (c == 0) //If count is equal to 0, exit. In order to prevent other threads from calling the countDown method after the counter value is 0, if there is no judgment, the status value will become negative.
            return false;
        int nextc = c-1; //For each execution, count minus one
        if (compareAndSetState(c, nextc)) //The cas mechanism is used to update the state of the state, and unsafe.compareAndSwapInt() is called to operate the memory. If the current state value is equal to the expected value, the atomic ground will set the synchronization state to the given updated value
            return nextc == 0; // Return when the update is successful
    }
}

private void doReleaseShared() {
    for (;;) {
        Node h = head;
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            if (ws == Node.SIGNAL) {//Indicates that subsequent threads need to be disconnected
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            // Cycle review
                unparkSuccessor(h);//Wake up subsequent nodes
            }
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        if (h == head)                   // loop if head changed
            break;
    }
}

await source code analysis:

After the current thread calls the await method of CountDownLatch object, the current thread will be blocked until one of the following situations occurs:

(1) When all threads call the countDown method of the CountDownLatch object, that is, when the timer value is 0.

(2) Other threads call the interrupt() method of the current thread to interrupt the current thread. The current thread will throw an InterruptedException exception and return.

public void await() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}

public final void acquireSharedInterruptibly(int arg) throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    if (tryAcquireShared(arg) < 0)//Try to see if the current count is 0. If it is 0, it will return directly. Otherwise, it will enter the AQS queue and wait
        doAcquireSharedInterruptibly(arg);
}

/**
 * CountDownLatch This method is rewritten by sync
 */
protected int tryAcquireShared(int acquires) {
    return (getState() == 0) ? 1 : -1;//If state is not equal to 0, it will return - 1. Enter the above method to join AQS queue and wait
}

//AQS waiting queue, using optimistic lock to obtain shared resources
private void doAcquireSharedInterruptibly(int arg) throws InterruptedException {
        final Node node = addWaiter(Node.SHARED);//addWaiter is the joining end of AQS
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();//Get previous node
                if (p == head) {
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                        //Set the queue header and check whether the subsequent processes may wait in shared mode. If so, propagation is performed when propagate > 0 or propagate status is set.
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        failed = false;
                        return;
                    }
                }
                //Check and modify the state of a node when it fails to acquire a lock. Returns true if the thread needs to block and suspend the current thread
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)//If the exit is abnormal, cancel the acquisition
                cancelAcquire(node);
        }
    }

Reference website:

https://www.cnblogs.com/huangjuncong/p/9275634.html
Http://ifeve.com/countdownload source code analysis/

Topics: Java less