Usage and scenarios of CountDownLatch, cyclicBarrier and semaphore

Keywords: Java Concurrent Programming

preface

In the previous two articles Synchronization queue for AQS,Conditional queue This article is to take a look at the three main concurrency tool classes in JUC: countDownLatch, cyclicBarrier and semaphore. This article will not expand from the perspective of source code as in the previous two articles, but take a look at the usage methods and scenarios of these tool classes in combination with the API of tools.

countDownLatch

countDownLatch can be understood as a counter. The initial value of the counter is the number of times a thread can execute (this is actually not equal to the number of threads, because a thread can execute multiple times). Whenever a thread is executed, the counter value is - 1. When the counter value is 0, it means that all threads are executed. These threads will be blocked before countDownLatch is reduced to 0. These threads will be stopped only when countDownLatch is reduced to 0.

countDownLatch generally has two usage scenarios: it enables a thread to wait for other threads to complete their work before continuing to execute; Of course, it can also be reversed. Other threads execute these threads after waiting for a thread to complete its work. ok, let's simulate these two scenarios.

Scenario 1: now there is a six person meeting. When six people gather, the meeting starts:

public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newCachedThreadPool();
        //The simulation needs enough six people to start the meeting
        CountDownLatch countDownLatch = new CountDownLatch(6);
        //count down
        for (int i=0;i<6;i++){
            int finalI = i+1;
            executorService.execute(()->{
                try {
                    Thread.sleep((long) (Math.random()*100));
                    System.out.println("personnel"+ finalI +"We have arrived in the conference room");
                    countDownLatch.countDown();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
        countDownLatch.await();
        System.out.println("Everyone is here. The meeting is over");
        executorService.shutdown();
}

There are two main methods of countdownlatch: countDown and await. countDown can reduce the defined countdownlatch value by one at a time. Await is used to block. The following programs of await will be executed only when the value of countdownlatch is reduced to 0.

Operation results:

Person 6 has arrived in the conference room
 Personnel 3 has arrived in the conference room
 Person 4 has arrived in the conference room
 Person 5 has arrived in the conference room
 Person 1 has arrived in the conference room
 Person 2 has arrived in the conference room
 Everyone is here. The meeting is over

What happens if I execute five threads at this time? As we said above, the value of countDownLatch will not be reduced to 0 at this time, so the program under await will not be executed.

Person 1 has arrived in the conference room
 Personnel 3 has arrived in the conference room
 Person 5 has arrived in the conference room
 Person 2 has arrived in the conference room
 Person 4 has arrived in the conference room

The second scenario: we simulate the running competition of athletes. We need to wait until the referee gives orders and start running

public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(1);
        for (int i=0;i<6;i++){
            int finalI = i+1;
            new Thread(()->{
                try {
                    countDownLatch.await();
                    Thread.sleep((long) Math.random()*1000);
                    System.out.println("Athletes"+ finalI +"Run to the end");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }
        //Simulated three second preparation time
        Thread.sleep(3000);
        countDownLatch.countDown();
        System.out.println("Referee starting gun");
}

cyclicBarrier

cyclicBarrier can be translated into loop fence and loop barrier. It can make a group of threads wait for each other and carry out subsequent operations after all threads reach a barrier point. I should have seen seven dragon balls when I was a child. Only when the seven dragon balls are gathered can we summon the dragon. Next, let's take seven dragon balls as an example to show the cyclicBarrier.

public static void main(String[] args) throws BrokenBarrierException, InterruptedException {
        CyclicBarrier barrier=new CyclicBarrier(7,()->{
            System.out.println("Collect seven dragon balls to summon the divine dragon");
        });
        for (int i=1;i<=7;i++){
            int num=i;
            new Thread(()->{
                try {
                    System.out.println("Collected page"+num+"Dragon Ball");
                    barrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            },String.valueOf(i)).start();
        }
}

It can be seen that when new CyclicBarrier, it has two parameters, one is the value of CyclicBarrier, and the other is a runnable function, which means that all threads will execute the runnable function when they reach the fence.

See the following effect:

The third dragon ball has been collected
 The sixth dragon ball has been collected
 The first dragon ball has been collected
 The Seventh Dragon ball has been collected
 The fourth dragon ball has been collected
 The fifth dragon ball has been collected
 The second dragon ball has been collected
 Collect seven dragon balls to summon the divine dragon

We said earlier that the CyclicBarrier is a circular fence, but this example does not seem to reflect its circular characteristics, but some modifications can be made on this basis:

public static void main(String[] args) throws BrokenBarrierException, InterruptedException {
  CyclicBarrier cyclicBarrier=new CyclicBarrier(7,()->{
            System.out.println("Gather seven dragon balls and summon the dragon");
            System.out.println("The dragon has been summoned. Collect dragon beads again three years later");
        });
        for (int i=0;i<7;i++){
            int finalI = i;
            new Thread(()->{
                try {
                    System.out.println(Thread.currentThread().getName()+"Collected to page"+ finalI +"Dragon Ball");
                    cyclicBarrier.await();
                    Thread.sleep(2000);
                    System.out.println(Thread.currentThread().getName()+"Received to page"+finalI+"Dragon Ball");
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }

    }

In this way, when all threads reach the fence, they will reset the CyclicBarrier.

Thread-1 Collected the first Dragon Ball
Thread-4 The fourth dragon ball was collected
Thread-2 The second dragon ball was collected
Thread-5 The fifth dragon ball was collected
Thread-6 The sixth dragon ball was collected
Thread-0 Collected the 0th Dragon Ball
Thread-3 The third dragon ball was collected
 Gather seven dragon balls and summon the dragon
 The dragon has been summoned. Collect dragon beads again three years later
Thread-5 Received the fifth Dragon Ball
Thread-6 Received the 6th Dragon Ball
Thread-3 Received the third dragon ball
Thread-2 Received the second Dragon Ball
Thread-4 Received the fourth Dragon Ball
Thread-0 Received the 0 Dragon Ball
Thread-1 Received the first Dragon Ball
 Gather seven dragon balls and summon the dragon
 The dragon has been summoned. Collect dragon beads again three years later

Through the above example, it is found that countDownLatch and CyclicBarrier are similar in use. Since they are similar, just use countDownLatch.

Of course, there are still some differences. First, countDownLatch can only be used once, while CyclicBarrier can be recycled. This is also reflected in the above example.

Secondly, the CyclicBarrier counter is controlled by itself, while the CountDownLatch counter is controlled by the user. In the CyclicBarrier, the thread calling the await method will not only block itself, but also reduce the counter by 1. In the CountDownLatch, the thread calling the await method will only block itself without reducing the counter value. Moreover, CyclicBarrier also provides more methods and is more flexible to use.

semaphore

Semaphore means semaphore and semaphore. Semaphore can be used to control the number of threads accessing specific resources at the same time, and ensure the rational use of resources by coordinating each thread.

For example, it can be understood as the display screen standing at the entrance of our parking lot. For every car entering the parking lot, the display screen will show the remaining parking spaces minus 1. For every car leaving the parking lot, the remaining vehicles displayed on the display screen will increase 1. When the remaining parking spaces on the display screen are 0, the vehicles cannot enter the parking lot, Until a car goes out of the parking lot. It can also be seen that semaphore can be used in scenarios with quantitative access restrictions. Next, let's simulate how to use semaphore with code.

public static void main(String[] args) {
        Semaphore semaphore=new Semaphore(3);//Available resources
        for (int i=1;i<=10;i++){
            new Thread(()->{
                try {
                    System.out.println("===="+Thread.currentThread().getName()+"Come to the parking lot");
                    if (semaphore.availablePermits()==0){
                        System.out.println("There is no available parking space in the parking lot");
                    }
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName()+"Successfully entered the parking lot");
                    TimeUnit.SECONDS.sleep(1);
                    semaphore.release();
                    System.out.println(Thread.currentThread().getName()+"Leave the parking lot");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            },String.valueOf(i)+"Car No").start();
        }
    }

When using Semaphore, the core methods are acquire and release. The acquire method can be regarded as obtaining a resource, while the release method releases a resource. In addition, it also provides some other methods to make our use of Semaphore more flexible.

====4 Car No. 1 came to the parking lot
====9 Car No. 1 came to the parking lot
====1 Car No. 1 came to the parking lot
====8 Car No. 1 came to the parking lot
9 Car No. 1 successfully entered the parking lot
====7 Car No. 1 came to the parking lot
====3 Car No. 1 came to the parking lot
 There is no available parking space in the parking lot
====2 Car No. 1 came to the parking lot
 There is no available parking space in the parking lot
====6 Car No. 1 came to the parking lot
 There is no available parking space in the parking lot
====5 Car No. 1 came to the parking lot
 There is no available parking space in the parking lot
 There is no available parking space in the parking lot
 There is no available parking space in the parking lot
1 Car No. 1 successfully entered the parking lot
4 Car No. 1 successfully entered the parking lot
====10 Car No. 1 came to the parking lot
 There is no available parking space in the parking lot
1 Car No. 1 leaves the parking lot
3 Car No. 1 successfully entered the parking lot
9 Car No. 1 leaves the parking lot
2 Car No. 1 successfully entered the parking lot
4 Car No. 1 leaves the parking lot
6 Car No. 1 successfully entered the parking lot
3 Car No. 1 leaves the parking lot
5 Car No. 1 successfully entered the parking lot
2 Car No. 1 leaves the parking lot
7 Car No. 1 successfully entered the parking lot
6 Car No. 1 leaves the parking lot
8 Car No. 1 successfully entered the parking lot
7 Car No. 1 leaves the parking lot
10 Car No. 1 successfully entered the parking lot
5 Car No. 1 leaves the parking lot
8 Car No. 1 leaves the parking lot
10 Car No. 1 leaves the parking lot

From the above results, it is still in line with the introduction of semaphore.

ending

The above are some usage methods and scenarios of the three concurrent tool classes. Generally speaking, they are not complex, but they can't be seen without practice. I think we can deepen our understanding by writing some examples or using them in the project on the basis of these examples.

Posted by ChompGator on Mon, 20 Sep 2021 10:12:29 -0700