JUC’s three commonly used helper classes –CountDownLatch, CyclicBarrier, and Semaphore — solve concurrency problems

Why introduce JUC’s powerful utility classes here? CountDownLatch | CyclicBarrier | Semaphore bottom are AQS

1. Reduce CountDownLatch

1.1 overview,

The CountDownLatch class can set a counter, then decrement by one with the countDown method, wait with await method until the counter is less than zero, and then continue with the statement following await method.

countDownLatchFunctions with

  • CountDownLatch has two main methods when invoked by one or more threadsawaitMethod, these threads wait to block.
  • Other thread callscountDownMethod decrement the counter by one (the thread calling the countDown method does not block)
  • When the value of the counter changes to 0, the value ofawaitThe thread that blocks the method is woken up to continue execution.

The constructor:

CountDownLatch(int count)  // Construct a timer to initialize CountDownLatch with the given count
Copy the code

Methods:

Two main methods are commonly used

await() // Causes the current thread to wait until the latch counts back to zero, unless the thread is interrupted.

countDown() // Decrement the count of the latch. If the count reaches zero, all waiting threads are released.
Copy the code

1.2. Case demonstration

The monitor can only lock the door after 6 students leave the classroom one after another. If the CountDownLatch class is not added, thread chaos will occur and the monitor will lock the door before the students leave the classroom, as follows:

Common implementation

public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        // Start 6 threads after 6 students have left the classroom.
        for (int i = 1; i <=6; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"Student Number left the classroom.");

            },String.valueOf(i)).start();
        }

        System.out.println(Thread.currentThread().getName()+"Squad leader locks the door and leaves."); }}Copy the code

The execution result

Student no. 1 left the classroom. Student No. 5 left the classroom. Student No. 4 left the classroom. Student No. 2 left the classroomCopy the code

In this case, we don’t know which thread will execute first, just preempt the CPU to execute.

useCountDownLatchAfter the specific correct case code

public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        
        // Create the CountDownLatch object and set the initial value
        CountDownLatch countDownLatch = new CountDownLatch(6);
        
        // Six students left the classroom one after another
        for (int i = 1; i <=6; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"Student Number left the classroom.");
                // Count -1, the thread calling countDown will not block
                countDownLatch.countDown();
            },String.valueOf(i)).start();
        }

        // Wait, the main thread will block while the counter changes from 6 to 0 until it reaches 0
        countDownLatch.await();
        System.out.println(Thread.currentThread().getName()+"Squad leader locks the door and leaves."); }}Copy the code

Conclusion:

  • Each student goes out once, then executescountDown()Method is subtracted by one and is calledcountDownMethod child threads do not block
  • The main thread will not execute because it is called by the main threadawait()Method, while the counter goes from 6 to 0, the main thread will block and wait until it goes to 0
  • Let some threads block until another thread completes a sequence of operations. The main thread must wait for the first six threads to complete their work before starting on its own.

1.3 Application Scenarios

From: zhuanlan.zhihu.com/p/148231820…

Typical application scenarios

For example, in parallel computing, when a certain processing has a large amount of computation, the calculation task can be divided into multiple sub-tasks. After all the sub-tasks are completed, the thread of the parent task will get the calculation results of all the sub-tasks and summarize them.

Take a look at CountDownLatch’s source code comments;

/** * A synchronization aid that allows one or more threads to wait until * a set of operations being performed in other  threads completes. * *@since 1.5
 * @author Doug Lea
 */
public class CountDownLatch {}Copy the code

It is a synchronization utility class that allows one or more threads to wait until other threads have finished executing.

Through the description, it can be clearly seen that CountDownLatch is used in two scenarios:

  • Scenario 1: Make multiple threads wait
  • Scenario 2: And make a single thread wait.

Scenario 1: Let multiple threads wait: Simulate concurrency and let concurrent threads execute together

To simulate high concurrency, have a group of threads perform a snap at a specified time (seckill time). These threads wait(countdownlatch.await ()) when they are ready until seckill time arrives, and then rush in. This is also a simple implementation of local test interface concurrency.

In this scenario, CountDownLatch acts as a starting gun; Just like in track and field race, the athletes will do the preparatory action at the starting line, wait for the sound of the starting gun, the athletes will run hard. Similar to the seckill scenario above, the code implementation is as follows:

CountDownLatch countDownLatch = new CountDownLatch(1);
for (int i = 0; i < 5; i++) {
    new Thread(() -> {
        try {
            // Ready... The players are jammed here, waiting for the call
            countDownLatch.await();
            String parter = "【" + Thread.currentThread().getName() + "】";
            System.out.println(parter + "Commence execution...");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }).start();
}

Thread.sleep(2000);// The referee is ready to give the call
countDownLatch.countDown();// Starting gun: execute the command
Copy the code

Running results:

【 thread-0 】 Start executing...... 【 THread-1 】 Start executing...... 【 THread-4 】 Start executing...... 【 THread-3 】 Start executing...... 【 THread-2 】 Start executing......Copy the code

We use countdownlatch.await () to block and wait for multiple participant threads to start, then call countdownlatch.countdown (1) on the main thread to reduce the count to zero and let all threads execute together; In this way, multiple threads execute concurrently at the same time to simulate the purpose of concurrent requests.

Scenario 2: Let a single thread wait: After multiple threads (tasks) complete, merge the totals

This is also our case study

A lot of times, we have a contextual dependency on concurrent tasks; For example, the data detail page needs to call multiple interfaces to obtain data at the same time, and the results need to be merged after obtaining data in concurrent requests. Or after multiple data operations are completed, data check is required. It’s all about summarizing and merging scenarios after multiple threads (tasks) have completed.

The code implementation is as follows:

CountDownLatch countDownLatch = new CountDownLatch(5);
for (int i = 0; i < 5; i++) {
    final int index = i;
    new Thread(() -> {
        try {
            Thread.sleep(1000 + ThreadLocalRandom.current().nextInt(1000));
            System.out.println("finish" + index + Thread.currentThread().getName());
            countDownLatch.countDown();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }).start();
}

countDownLatch.await();// The main thread is blocking, and when the counter ==0, it wakes up the main thread to proceed.
System.out.println("Main thread: Summary of results after all tasks are completed.");
Copy the code

Running results:

Finish4thread-4 Finish1thread-1 Finish2thread-2 Finish3thread-3 Finish0thread-0 Main thread: Summarizes the results after all tasks are completedCopy the code

Add countdownlatch.countdown () to the last line of each thread’s completion to make the counter -1; When all threads finish -1 and the counter drops to 0, the main thread moves down to perform the summary task.

As you can see from the code in the two examples above, there are not many apis for CountDownLatch;

  • CountDownLatchConstructor of thecountIs the number of threads to wait for a lock. This value can only be set once and cannot be reset;
  • await(): The thread calling this method blocks until N passed in by the constructor decreases to 0.
  • countDown(): Decrease the CountDownLatch count by 1;

Try CountDownLatch to solve the time wait problem

public class AtomicIntegerDemo {
    
    AtomicInteger atomicInteger=new AtomicInteger(0);
    
    public void addPlusPlus(a){
        atomicInteger.incrementAndGet();
    }
    public static void main(String[] args) throws InterruptedException {
        
        CountDownLatch countDownLatch=new CountDownLatch(10);
        AtomicIntegerDemo atomic=new AtomicIntegerDemo();
        
        // 10 threads loop 100 times through addPlusPlus, resulting in 10*100=1000
        for (int i = 1; i <= 10; i++) {
            new Thread(()->{
               try{
                   for (int j = 1; j <= 100; j++) { atomic.addPlusPlus(); }}finally {
                   countDownLatch.countDown();
               }
            },String.valueOf(i)).start();
        }
        //(1). If you do not add the following pause time of 3 seconds, the main thread will end before i++ 1000 times
        //try { TimeUnit.SECONDS.sleep(3);  } catch (InterruptedException e) {e.printStackTrace();}
        //(2). Use CountDownLatch to solve the waiting time problem
        countDownLatch.await();
        System.out.println(Thread.currentThread().getName()+"\t"+"Result obtained :"+atomic.atomicInteger.get()); }}Copy the code

1.4. Underlying principles

2, CyclicBarrier

2.1 overview,

This class is a synchronization helper class that allows a group of threads to wait for each other until some common barrier point is reached. In programs that design a group of threads of a fixed size that must wait for each other, this class is useful because the barrier can be reused after releasing the waiting threads, and is called a cyclic barrier.

Cyclicbarriers literally mean barriers that can be used cyclically. All it does is make a group of threads block when they reach a barrier (also known as a synchronization point), and the barrier will not open until the last thread reaches the barrier, and all threads blocked by the barrier will continue to work, and the thread enters the barrier through the await() method of the CyclicBarrier

Common constructors are:

CyclicBarrier(intParties, Runnable barrierAction)// Create a new CyclicBarrier that starts when a given number of participants (threads) are in the wait state and performs the given barrier action when the barrier is started, performed by the last thread to enter the barrier.
Copy the code

Methods:

Common methods are:

await()  // Wait until all participants have called the await method on the barrier.
Copy the code

2.2. Case demonstration

Collect 7 dragon balls to summon the divine dragon

public class CyclicBarrierDemo {

    // Create a fixed value
    private static final int NUMBER = 7;

    public static void main(String[] args) {
        
        / / create the CyclicBarrier
        CyclicBarrier cyclicBarrier =
            new CyclicBarrier(NUMBER,()->{
                System.out.println("***** Collect 7 dragon balls to summon the divine Dragon.");
            });

        // The process of collecting seven dragon balls
        for (int i = 1; i <=7; i++) {
            new Thread(()->{
                try {
                    System.out.println(Thread.currentThread().getName()+"Stardragons have been collected.");
                    // Each thread will wait to block in the fence until all 7 threads have executed the await method,
                    // Actions inside the fence are performed by the last thread to enter the fence
                    cyclicBarrier.await();
                } catch(Exception e) { e.printStackTrace(); } },String.valueOf(i)).start(); }}}Copy the code

The execution result

1Star dragons have been collected6Star dragons have been collected4Star dragons have been collected5Star dragons have been collected2Star dragons have been collected7Star dragons have been collected3Star Dragons have been collected at ***********7A dragon ball can summon a dragonCopy the code

Conclusion:

The first argument to the constructor of a CyclicBarrier is the target barrier number, which is incremented each time a CyclicBarrier is executed. If the target barrier number is reached, the statement following CyclicBarrier.await() is executed. You can think of a CyclicBarrier as a plus one operation.

2.3 Underlying principles

3, Semaphore

3.1 overview,

A counting semaphore. Conceptually, the semaphore maintains a license set, blocking each thread until the license is available if necessary, acquire() acquires the license, and add a license for each release(), possibly freeing a blocking acquirer. However, instead of using actual license objects, Semaphore only counts the number of licenses available and acts accordingly.

Specific commonly used construction methods are:

Semaphore(int permits)  // Create Semapore with a given number of permissions and unfair fair Settings.
Copy the code

Specific commonly used methods are:

acquire()  // Obtain a license from this semaphore, other threads block before providing a license to another thread, otherwise the thread is interrupted
release() // Release a permit and return it to the semaphore.
Copy the code

Semaphore Semaphore = new Semaphore(3); Normally acquire() throws an exception, and release is executed in finally

  • When a thread invokes acquire, it either acquires a semaphore successfully (minus 1) or waits until a thread releases a semaphore or times out.
  • Release actually increments the semaphore value by one and wakes up the waiting thread.
  • Semaphores are mainly used for two purposes, one is for mutually exclusive use of multiple shared resources and the other is for control of the number of concurrent threads.

3.2. Case demonstration

Six cars, three parking Spaces

public class SemaphoreDemo {
    public static void main(String[] args) {
        
        // Create Semaphore and set the number of permissions
        Semaphore semaphore = new Semaphore(3);

        // Simulate 6 cars
        for (int i = 1; i <=6; i++) {
            new Thread(()->{
                try {
                    // Preempt, obtain a permission, other threads are blocked, equivalent to a lock
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName()+"Grabbed a parking spot.");
                    // Set random stop time
                    TimeUnit.SECONDS.sleep(new Random().nextInt(5));
                    System.out.println(Thread.currentThread().getName()+"------ left the parking space.");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    // Release permission, no matter how long the parking time,
                    // The permissions must eventually be released back to the Semaphore Semaphore for other threads to use, similar to thread pools.semaphore.release(); } },String.valueOf(i)).start(); }}}Copy the code

Execution Result:

1 grabbed a parking space 3 grabbed a parking space 2 grabbed a parking space 3 ------ left a parking space 4 grabbed a parking space 1 ------ left a parking space 5 ------ left a parking space 5 ------ left a parking space 6 ------ left a parking space 6 ------ left a parking space 4 ------ left the parking spaceCopy the code

3.3 Underlying principles