Explore Java Concurrency-Synchronization Tools

Explore Java Concurrency-Synchronization Tools

The synchronization tool class is a general concept. It can coordinate the control flow of the thread according to its own state. Understanding the commonly used synchronization tools in JAVA can help developers write better concurrent code.

CountDownLatch

The function of a lock is equivalent to a door. Before the door is opened, any thread execution here will be mercilessly detained. Until someone opens the door, the threads blocked outside the door will continue the process behind the door. .

import java.util.concurrent.CountDownLatch;

public class Main {

    public static void main(String[] args) throws InterruptedException {
       //The first door needs to be triggered once to open
        CountDownLatch firstDoor = new CountDownLatch(1);
       //The second door needs 3 triggers to open
        CountDownLatch lastDoor = new CountDownLatch(3);
       //Start 3 threads
        for (int i = 1; i <= 3; i++) {
            int id = i;
            new Thread(() -> {
                try {
                    System.out.println(id + "The pretty boy arrived in front of the first door");
                    firstDoor.await();
                    Thread.sleep((long) (Math.random() * 5000));//Random take a break
                    System.out.println(id + "The pretty boy arrives in front of the second door and triggers");
                    lastDoor.countDown();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }
        Thread.sleep(1000);
       //The main thread opens the first door, allowing other threads to pass
        firstDoor.countDown();
       //Wait for other threads to work together to open the second door
        lastDoor.await();
        System.out.println("bye~");
    }
}

/* Output:

Pretty boy No. 1 arrives in front of the first door
Pretty boy No. 3 arrives in front of the first door
Pretty boy No. 2 arrives in front of the first door
Pretty boy #1 arrives in front of the second door and triggers
Pretty boy #2 arrives in front of the second door and triggers
Pretty boy #3 arrives in front of the second door and triggers
bye~

*/

Block FutureTask

FutureTask can also be used as a lock, often used with thread pools. After submitting to the thread pool, the main thread (the thread submitting the task) calls the get() method to block the thread until the asynchronous task is executed or timed out.

import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class Main {

    public static void main(String[] args) throws InterruptedException, ExecutionException {
       //Method 1: Pass in a Callable, that is, return directly after the business code is executed
        FutureTask<Liangzai> callableTask = new FutureTask<>(new Callable<Liangzai>() {
            @Override
            public Liangzai call() throws Exception {
                Thread.sleep(1000);
                Liangzai liangzai = new Liangzai();
                liangzai.name = "callable";
                return liangzai;
            }
        });

       //Method 2: Pass in a runnable and an object to store the result, modify the result object in the runnable
        Liangzai runableLiangzai = new Liangzai();
        FutureTask<Liangzai> runableTask = new FutureTask<>(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                    runableLiangzai.name = "runable";
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, runableLiangzai);

       //Name the pretty boy asynchronously
        new Thread(callableTask).start();
       //Wait for the name to be confirmed
        Liangzai liangzai = callableTask.get();
        System.out.println(liangzai);

       //Name the pretty boy asynchronously
        new Thread(runableTask).start();
       //Wait for the name to be confirmed
        liangzai = runableTask.get();
        System.out.println(liangzai);

    }

    public static class Liangzai {

        String name;

        @Override
        public String toString() {
            return "Liangzai{" +
                    "name='" + name +'\'' +
                    '}';
        }
    }
}

/* Output:

Liangzai{name='callable'}
Liangzai{name='runable'}

*/

Semaphore

Semaphores are used to control the number of threads that "access resources" or "execute operations" at the same time, and can also be used to implement resource pools. It manages a set of virtual "permissions". Whenever a thread wants to access a particular resource, it needs to apply for a permission first, and then return the permission after it is used up. If the permission is insufficient, it will be blocked.

import java.util.concurrent.*;

public class Main {

    public static void main(String[] args) throws InterruptedException, ExecutionException {
       //Initialize 2 licenses
        Semaphore semaphore = new Semaphore(2);

       //Don't want to use thread pool, use this to realize the main thread and wait for all sub-threads to finish executing
        int n = 10;
        CountDownLatch countDownLatch = new CountDownLatch(n);
        for (int i = 1; i <= n; i++) {
            int id = i;
            new Thread(() -> {
                try {
                   //apply for a permit
                    System.out.println(id + "I want 1 license");
                    semaphore.acquire();
                    System.out.println(id + "Number gets 1 license");

                   //Hold the permit for a period of time
                    Thread.sleep((long) (Math.random() * 10000));

                   //return permission
                    semaphore.release();
                    System.out.println(id + "number return 1 license");

                   //This thread is finished
                    countDownLatch.countDown();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }
        countDownLatch.await();
        System.out.println("done~");
    }
}

/* Output:

Number 1 wants 1 license
Number 2 wants 1 license
Number 1 gets 1 license
No. 2 gets 1 license
No. 3 wants 1 license
Number 4 wants 1 license
Number 5 wants 1 license
Number 6 wants 1 license
No. 7 wants 1 license
No. 8 wants 1 license
Number 9 wants 1 license
Number 10 wants 1 license
Return 1 license on the 2nd
No. 3 gets 1 license
Return 1 license on 1
No. 4 gets 1 license
Return 1 license on the 3rd
No. 5 gets 1 license
Return 1 license on the 4th
No. 6 gets 1 license
Return 1 license on the 5th
No. 7 gets 1 license
Return 1 license on the 6th
1 license on the 8th
Return 1 license on the 7th
9th got 1 license
Return 1 license on the 9th
1 license on the 10th
Return 1 license on the 10th
Return 1 license on the 8th
done~

*/

In the above code, since there are only two licenses, all the first two threads will get them as soon as they say they want to. The third one starts without a long wait. The next time a thread gets a license, one thread returns the license. after that.

CyclicBarrier

CyclicBarrier is similar to CountDownLatch in that it blocks a group of threads until a certain point in time. The difference is as follows.

  • Lock CountDownLatch: Wait for a group of threads to complete a certain task, and then wait for the thread to continue to perform subsequent actions. The status will not change after the end
  • CyclicBarrier: Wait for a group of threads to reach a certain position, and then the group of threads continue to perform subsequent actions, and the group of threads wait for each other. The status can be reset.
import java.util.concurrent.*;

public class Main {

    public static void main(String[] args) throws InterruptedException, ExecutionException {
       //Define a fence and wait for 3 people to arrive
        int n = 3;
        CyclicBarrier barrier = new CyclicBarrier(n, new Runnable() {
            @Override
            public void run() {
               //Operations performed after everyone is ready
                System.out.println("Everyone is here, start happy");
            }
        });

       //Don't want to use thread pool, use this to realize the main thread and wait for all sub-threads to finish executing
        CountDownLatch countDownLatch = new CountDownLatch(n);
        for (int i = 1; i <= n; i++) {
            int id = i;
            new Thread(() -> {
                try {
                   //It takes different time for everyone to go out and dress up
                    Thread.sleep((long) (Math.random() * 5000));

                   //wait for others to arrive here
                    System.out.println(id + ": I got to the place");
                    barrier.await();
                    System.out.println(id + ": let's go");

                   //This thread is finished
                    countDownLatch.countDown();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
        }
        countDownLatch.await();
        System.out.println("done~");
    }
}

/* Output:

3: I got to the place
2: I got to the place
1: I have arrived
Everyone is here, start happy
1: let's go
3: let's go
2: let's go
done~

*/

Fence Exchanger

Exchanger is a two-party fence used to exchange data between two parties. After one party sends a message, it will block until the other party receives the message and returns a message, achieving a harmonious communication of one person, one sentence. It is useful when the operations of the two parties are asymmetrical, for example, one thread writes data to the buffer, and one thread reads data from the buffer.

import java.util.concurrent.*;

public class Main {

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        Exchanger<String> exchanger = new Exchanger<>();
        CountDownLatch countDownLatch = new CountDownLatch(2);
        new Thread(() -> {
            try {
                for (int i = 0; i <5; i++) {
                    Thread.sleep((long) (Math.random() * 5000));
                    String fromKun = exchanger.exchange("I am a small dish, No." + i);
                    System.out.println("Xiao Cai received a message:" + fromKun);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            countDownLatch.countDown();
        }).start();
        new Thread(() -> {
            try {
                for (int i = 0; i <5; i++) {
                    Thread.sleep((long) (Math.random() * 5000));
                    String fromKun = exchanger.exchange("I am A Kun, No." + i);
                    System.out.println("A Kun received the message:" + fromKun);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            countDownLatch.countDown();
        }).start();
        countDownLatch.await();
        System.out.println("done~");
    }
}

/* Output:

Xiaocai received news: I am Akun, No.0
A Kun received the news: I am Xiaocai, No.0
A Kun received the news: I am Xiaocai, No.1
Xiaocai received news: I am A Kun, No.1
A Kun received the news: I am Xiaocai, No.2
Xiaocai received news: I’m A Kun, No. 2
Xiaocai received news: I am A Kun, No.3
A Kun received the news: I am Xiaocai, No.3
A Kun received the news: I am Xiaocai, No.4
Xiaocai received news: I’m Akun, No.4
done~

*/

summary

The so-called synchronization tool classes do not specifically refer to classes that implement a certain function. Their main value lies in helping multiple threads to better cooperate with each other to complete the work. As long as you are clear about your own needs and familiar with JAVA's own classes, You can choose the appropriate synchronization tool class, and even implement the synchronization tool class you need.