[concurrent] CountDownLatch sorting

1. introduction

  • CountDownLatch is a synchronization tool class that allows one or more threads to wait until other threads have finished executing.
    • CountDownLatch was introduced in JDK 1.5.
    • It exists under the java.util.concurrent package.
    • For example, the main thread of an application wants to execute after the thread responsible for starting the framework service has started all the framework services.

2. Principle of countdownlatch

  • CountDownLatch is implemented by a counter whose initial value is the number of threads.
    • Every time a thread finishes its task, the counter value is decremented by 1.
    • When the counter value reaches 0, it means that all threads have completed the task, and then the thread waiting on the lock can resume executing the task.

CountDownLatch principle

Pseudocode for CountDownLatch

//Main thread start
//Create CountDownLatch for N threads
//Create and start N threads
//Main thread wait on latch
//N threads completes there tasks are returns
//Main thread resume execution

Constructor

//Constructs a CountDownLatch initialized with the given count.
public CountDownLatch(int count) {...}
  • This count is essentially the number of threads that need to wait for a lock.
    • This value can only be set once, and CountDownLatch does not provide any other mechanism to reset this count.
  • The first interaction with CountDownLatch was with the main thread waiting for another thread.
    • This main thread must call the CountDownLatch.await() method immediately after starting other threads, so that the operation of the main thread will block on this method until other threads complete their tasks.
  • The other N threads must refer to the lock object because they need to notify the CountDownLatch object if they have completed the task.
    • This notification is done through the CountDownLatch.countDown() method, reducing the count by 1 per call.
    • When all N threads call this method, the count will reach 0, and the main thread can continue to execute after the await() method.
  • CountDownLatch and ReentrantLock Similarly, Sync inheritance is used internally AQS.
  • The constructor passes the count value to Sync and sets state.
public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
}
......
Sync(int count) {
    setState(count);
}

Blocking thread

await method

  • acquireSharedInterruptibly() of AQS is called directly.
    • First, judge whether it is interrupted, and the interrupt throws an exception.
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)
        doAcquireSharedInterruptibly(arg);
}

tryAcquireShared method

  • First, we try to obtain the shared lock, which is similar to the exclusive lock. CountDownLatch implements the judgment logic.
    • State state variable. The value of state represents the number of threads to reach the condition. For example, initialization is 5, which means the number of threads to reach the condition is 5. Every call to countDown() function will reduce 1.
protected int tryAcquireShared(int acquires) {
   return (getState() == 0) ? 1 : -1;
}
  • Return 1 for success and - 1 for failure. If the get fails, you need to call doacquireshared interrupt().

Release operation

  • The countDown operation is actually the operation to release the lock. Each time it is called, the count value is reduced by 1.
public void countDown() {
    sync.releaseShared(1);
}
  • It is also the first attempt to release the lock, which is implemented in CountDownLatch.
public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}
......
protected boolean tryReleaseShared(int releases) {
    // Decrement count; signal when transition to zero
    for (;;) {
        int c = getState();
        if (c == 0)
            return false;
        int nextc = c-1;
        if (compareAndSetState(c, nextc))
            return nextc == 0;
    }
}
  • The method of dead loop plus CAS ensures that the state minus 1 operation. When the count value is equal to 0, it means that all sub threads have finished executing. The thread blocked by await() then calls doReleaseShared() to wake up.

await with limited time

  • CountDownLatch's wait method also has a version that limits blocking time.
public boolean await(long timeout, TimeUnit unit)
    throws InterruptedException {
    return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
  • Finally, the doAcquireSharedNanos() method is called. The difference is only time processing.

3. Use scenario of countdownlatch

Maximize parallelism

  • Want to start multiple threads at the same time to achieve maximum parallelism.
    • For example, you want to test a singleton class. If you create a CountDownLatch with an initial counter of 1 and let all other threads wait on the lock, you can call the countDown() method once to allow all other waiting threads to resume execution at the same time.

Wait for N threads to complete their tasks before execution

  • For example, the application startup class should ensure that all N external systems are started and running before processing the user request.

Deadlock detection

  • N threads are used to access shared resources. The number of threads is different in each test phase, and deadlock is attempted.

4. Use case of countdownlatch

  • Simulate an application startup class, start N threads at the beginning, check whether N external services are normal and notify locking.
    • The startup class waits on the lock all the time. Once all the external services are verified and checked, the startup class execution is resumed.

BaseHealthChecker

  • Implements the Runnable interface, which is the base class responsible for all specific external service health checks.
import java.util.concurrent.CountDownLatch;

public abstract class BaseHealthChecker implements Runnable {
    
    private CountDownLatch _latch;
    private String _serviceName;
    private boolean _serviceUp;
    
    public BaseHealthChecker(String serviceName, CountDownLatch latch)
    {
        super();
        this._latch = latch;
        this._serviceName = serviceName;
        this._serviceUp = false;
    }

    @Override
    public void run() {
        try {
            verifyService();
            _serviceUp = true;
        } catch (Throwable t) {
            t.printStackTrace(System.err);
            _serviceUp = false;
        } finally {
            if(_latch != null) {
                _latch.countDown();
            }
        }
    }

    public String getServiceName() {
        return _serviceName;
    }

    public boolean isServiceUp() {
        return _serviceUp;
    }
    
    public abstract void verifyService();
}
  • The following three classes inherit from BaseHealthChecker and refer to CountDownLatch instance. Except for different service name and sleep time, they implement their verifyService() methods.

NetworkHealthChecker

import java.util.concurrent.CountDownLatch;

public class NetworkHealthChecker extends BaseHealthChecker
{
    public NetworkHealthChecker (CountDownLatch latch)
    {
        super("Network Service", latch);
    }
    
    @Override
    public void verifyService() 
    {
        System.out.println("Checking " + this.getServiceName());
        try 
        {
            Thread.sleep(7000);
        } 
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        System.out.println(this.getServiceName() + " is UP");
    }
}

DatabaseHealthChecker

import java.util.concurrent.CountDownLatch;

public class DatabaseHealthChecker extends BaseHealthChecker
{
    public DatabaseHealthChecker (CountDownLatch latch)
    {
        super("Database Service", latch);
    }
    
    @Override
    public void verifyService() 
    {
        System.out.println("Checking " + this.getServiceName());
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(this.getServiceName() + " is UP");
    }
}

CacheHealthChecker

import java.util.concurrent.CountDownLatch;

public class CacheHealthChecker extends BaseHealthChecker
{
    public CacheHealthChecker (CountDownLatch latch)
    {
        super("Cache Service", latch);
    }
    
    @Override
    public void verifyService() 
    {
        System.out.println("Checking " + this.getServiceName());
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(this.getServiceName() + " is UP");
    }
}

ApplicationStartupUtil

  • A main startup class is responsible for initializing the locking, and then waiting for all services to be checked before resuming execution.
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

public class ApplicationStartupUtil 
{
    private static List<BaseHealthChecker> _services;
    private static CountDownLatch _latch;
    
    private ApplicationStartupUtil()
    {
    }
    
    private final static ApplicationStartupUtil INSTANCE = new ApplicationStartupUtil();
    
    public static ApplicationStartupUtil getInstance()
    {
        return INSTANCE;
    }
    
    public static boolean checkExternalServices() throws Exception
    {
        _latch = new CountDownLatch(3);
        _services = new ArrayList<BaseHealthChecker>();
        _services.add(new NetworkHealthChecker(_latch));
        _services.add(new CacheHealthChecker(_latch));
        _services.add(new DatabaseHealthChecker(_latch));
        
        Executor executor = Executors.newFixedThreadPool(_services.size());
        
        for(final BaseHealthChecker v : _services) 
        {
            executor.execute(v);
        }
        
        _latch.await();
        
        for(final BaseHealthChecker v : _services) 
        {
            if( ! v.isServiceUp())
            {
                return false;
            }
        }
        return true;
    }
}

Test code

public class Main {
    public static void main(String[] args) 
    {
        boolean result = false;
        try {
            result = ApplicationStartupUtil.checkExternalServices();
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("External services validation completed !! Result was :: "+ result);
    }
}

/** --- print ---
Checking Network Service
Checking Cache Service
Checking Database Service
Database Service is UP
Cache Service is UP
Network Service is UP
External services validation completed !! Result was :: true
**/

5. summary

  • CountDownLatch is one-time. The counter value can only be initialized once in the construction method. After that, there is no mechanism to set the value again. When CountDownLatch is used, it cannot be used again.
  • The join() method of Thread can achieve the same function, but when the Thread pool is used, the join() method cannot, and CountDownLatch can still achieve the function.
package concurrent;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.*;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
 
import java.util.*;
public class CountDownLatchDemo {
    
    private final static CountDownLatch cdl=new CountDownLatch(3);
    private final static Vector v=new Vector();
    private final static ThreadPoolExecutor threadPool= new ThreadPoolExecutor(10, 15, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());//Use thread pool
    
    private static class WriteThread extends Thread{
        private final String writeThreadName;
        private final int stopTime;
        private final String str;
        public WriteThread(String name,int time,String str)
        {
            this.writeThreadName=name;
            this.stopTime=time;
            this.str=str;
        }
        public void run()
        {
            System.out.println(writeThreadName+"Start writing");
            try
            {
                Thread.sleep(stopTime);
            }
            catch(InterruptedException e)
            {
                e.printStackTrace();
            }
            cdl.countDown();
            v.add(str);
            System.out.println(writeThreadName+"The contents to be written are:"+str+". End of writing!");
        }
    }
    private static class ReadThread extends Thread{
        public void run()
        {
            System.out.println("Write before read");
            try
            {
                cdl.await();//The thread waits until countDown is reduced to 0, and then wakes up one by one.
                //Thread.sleep(3000);
            }
            catch(InterruptedException e)
            {
                e.printStackTrace();
            }
            for(int i=0;i<v.size();i++)
            {
                System.out.println("Read the first"+(i+1)+"Records are:"+v.get(i));
            }
            System.out.println("End of read operation!");
        }
    }
 
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Thread read=new ReadThread();
        threadPool.execute(read);
        String[] str= {"Multi thread knowledge point","Multithreading CountDownLatch Knowledge points","Control order in multithreading can be used CountDownLatch"};
        
 
        for(int i=0;i<3;i++)
        {
            Thread t1= new WriteThread("writeThread"+(i+1),1000*(i+1),str[i]);
               
            
           
            threadPool.execute(t1);
            
        }
        
        //new WriteThread("writeThread1",1000, "multi thread knowledge point"). start();
        //new WriteThread("writeThread2",2000, "knowledge point of multithreaded CountDownLatch"). start();
        //new WriteThread("writeThread3",3000, "countdownlatch can be used for control order in multithreading"). Start();
 
    }
 
}
  • The CountDownLatch class mainly uses scenarios with obvious order requirements.
    • For example, ranking can only be calculated after running, and statistical work can only be done after all records are written. Therefore, CountDownLatch is a perfect logical function to make threads follow the correct logic.



Reference resources:

https://www.jianshu.com/p/e233bb37d2e6

 

 

77 original articles published, 29 praised, 280000 visitors+
Private letter follow

Tags: Java network Database JDK

Posted on Tue, 10 Mar 2020 22:50:24 -0400 by bobohob