Advanced synchronization tool in Java Concurrent package

Posted by unkwntech on Fri, 07 Feb 2020 10:31:40 +0100

The concurrent package in Java refers to the classes and interfaces under the java.util.concurrent package and its subpackages. It provides various functional supports for Java concurrency, such as:

  • The thread pool creation classes ThreadPoolExecutor and Executors are provided;
  • Various locks are provided, such as Lock, ReentrantLock, etc;
  • It provides various thread safe data structures, such as ConcurrentHashMap, LinkedBlockingQueue, DelayQueue, etc;
  • It provides more advanced thread synchronization structures, such as CountDownLatch, CyclicBarrier, Semaphore, etc.

In the previous chapters, we have introduced the use of thread pool, thread safe data structure, etc. in this paper, we will focus on the more advanced thread synchronization classes in Java Concurrent package: CountDownLatch, CyclicBarrier, Semaphore, Phaser, etc.

CountDownLatch introduction and use

CountDownLatch can be seen as a counter that can only be subtracted, allowing one or more threads to wait for execution.
CountDownLatch has two important methods:

  • countDown(): decrease the counter by 1;
  • await(): when the counter is not 0, the thread calling the method is blocked. When the counter is 0, one or all waiting threads can be awakened.

CountDownLatch usage scenario:
Take the situation in life, such as going to the hospital for physical examination. Generally, people will go to the hospital in line in advance, but only when the doctor starts to work, can the physical examination be officially started, and the doctor also needs to finish the physical examination for all people before going to work. In this case, CountDownLatch should be used, and the process is: Patients queuing → doctors going to work → physical examination completed → doctors going to work.

The sample code for CountDownLatch is as follows:

// Hospital atresia
CountDownLatch hospitalLatch = new CountDownLatch(1);
// Patient atresia
CountDownLatch patientLatch = new CountDownLatch(5);
System.out.println("Patient queuing");
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {
    final int j = i;
    executorService.execute(() -> {
        try {
            hospitalLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Physical examination:" + j);
        patientLatch.countDown();
    });
}
System.out.println("Doctors work");
hospitalLatch.countDown();
patientLatch.await();
System.out.println("The doctor leaves work.");
executorService.shutdown();

The results of the above procedures are as follows:

Patient queuing

Doctors work

Physical examination: 4

Physical examination: 0

Physical examination: 1

Physical examination: 3

Physical examination: 2

The doctor leaves work.

The execution flow is as follows:

Introduction and use of CyclicBarrier

Cyclic barrier enables a group of threads to wait for a certain condition to be met and execute at the same time.

The classic use scenario of CyclicBarrier is bus departure. In order to simplify our definition, each bus only needs four people to start, and the people from behind will queue up to follow the corresponding standards in turn.

Its construction method is CyclicBarrier(int parties,Runnable barrierAction). Among them, parties means that there are several threads participating in waiting, and barrierAction means the method triggered after the conditions are met. CyclicBarrier uses the await() method to identify that the current thread has reached the barrier point and is then blocked.

The example code of CyclicBarrier is as follows:

import java.util.concurrent.*;
public class CyclicBarrierTest {
    public static void main(String[] args) throws InterruptedException {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(4, new Runnable() {
            @Override
            public void run() {
                System.out.println("Departure.");
            }
        });
        for (int i = 0; i < 4; i++) {
            new Thread(new CyclicWorker(cyclicBarrier)).start();
        }
    }
    static class CyclicWorker implements Runnable {
        private CyclicBarrier cyclicBarrier;
        CyclicWorker(CyclicBarrier cyclicBarrier) {
            this.cyclicBarrier = cyclicBarrier;
        }
        @Override
        public void run() {
            for (int i = 0; i < 2; i++) {
                System.out.println("Passenger:" + i);
                try {
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

The results of the above procedures are as follows:

Passengers: 0

Passengers: 0

Passengers: 0

Passengers: 0

Departure.

Passengers: 1

Passengers: 1

Passengers: 1

Passengers: 1

Departure.

The execution flow is as follows:

Introduction and use of Semaphore

Semaphore is used to manage the access and use of control resources in multithreading. Semaphore is like a guard in a parking lot. It can control the use of parking space resources. Biruto has 5 cars, only 2 parking spaces. The guard can put two cars in first, and then let the car behind enter after the car comes out.

The example code of Semaphore is as follows:

Semaphore semaphore = new Semaphore(2);
ThreadPoolExecutor semaphoreThread = new ThreadPoolExecutor(10, 50, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
for (int i = 0; i < 5; i++) {
    semaphoreThread.execute(() -> {
        try {
            // Blocking access
            semaphore.acquire();
            System.out.println("Thread: " + Thread.currentThread().getName() + " Time:" + LocalDateTime.now());
            TimeUnit.SECONDS.sleep(2);
            // Release permit
            semaphore.release();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    });
}

The results of the above procedures are as follows:

Thread: pool-1-thread-1 Time: 2019-07-10 21:18:42

Thread: pool-1-thread-2 Time: 2019-07-10 21:18:42

Thread: pool-1-thread-3 Time: 2019-07-10 21:18:44

Thread: pool-1-thread-4 Time: 2019-07-10 21:18:44

Thread: pool-1-thread-5 Time: 2019-07-10 21:18:46

The execution flow is as follows:

Phase shifter is provided by JDK 7. Its function is to wait for all threads to arrive before continuing or starting a new set of tasks.

For example, for a tour group, we stipulate that all members must arrive at the designated place before they can take the bus to the scenic spot one. After arriving at the scenic spot, they can play independently. After that, they must all arrive at the designated place before they can continue to take the bus to the next scenic spot. Similar scenarios are very suitable for using phase.

The phase example code is as follows:

public class Lesson5\_6 {
    public static void main(String[] args) throws InterruptedException {
        Phaser phaser = new MyPhaser();
        PhaserWorker[] phaserWorkers = new PhaserWorker[5];
        for (int i = 0; i < phaserWorkers.length; i++) {
            phaserWorkers[i] = new PhaserWorker(phaser);
            // Number of threads waiting for register phase, number of threads waiting for one execution + 1
            phaser.register();
        }
        for (int i = 0; i < phaserWorkers.length; i++) {
            // Perform tasks
            new Thread(new PhaserWorker(phaser)).start();
        }
    }
    static class PhaserWorker implements Runnable {
        private final Phaser phaser;
        public PhaserWorker(Phaser phaser) {
            this.phaser = phaser;
        }
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + " | Arrive" );
            phaser.arriveAndAwaitAdvance(); // Gather and depart
            try {
                Thread.sleep(new Random().nextInt(5) * 1000);
                System.out.println(Thread.currentThread().getName() + " | Arrive" );
                phaser.arriveAndAwaitAdvance(); // Scenic spot 1 set up and depart
                Thread.sleep(new Random().nextInt(5) * 1000);
                System.out.println(Thread.currentThread().getName() + " | Arrive" );
                phaser.arriveAndAwaitAdvance(); // Scenic Spot 2 assemble and depart
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    // Event notification after the completion of each phase of Phaser
    static class MyPhaser extends Phaser{
        @Override
        protected boolean onAdvance(int phase, int registeredParties) { // Callback after each stage
            switch (phase) {
                case 0:
                    System.out.println("==== Gather and depart ====");
                    return false;
                case 1:
                    System.out.println("==== Scenic spot 1 is assembled and departs for the next scenic spot ====");
                    return false;
                case 2:
                    System.out.println("==== Scenic Spot 2: set up and leave for home ====");
                    return false;
                default:
                    return true;
            }
        }
    }
}

The results of the above procedures are as follows:

Thread-0 arrival

Thread-4 arrival

Thread-3 arrival

Thread-1 arrival

Thread-2 arrival

====Gather and depart====

Thread-0 | arrival

Thread-4 arrival

Thread-1 arrival

Thread-3 arrival

Thread-2 arrival

====Scenic spot 1 is assembled and departs for the next scenic spot====

Thread-4 arrival

Thread-3 arrival

Thread-2 arrival

Thread-1 arrival

Thread-0 arrival

====Scenic Spot 2: set up and leave for home====

Related interview questions

1. Which of the following classes is used to control the access rights of a group of resources?

A: Phaser
B: Semaphore
C: CountDownLatch
D: CyclicBarrier

Answer: B

2. Which of the following classes cannot be reused?

A: Phaser
B: Semaphore
C: CountDownLatch
D: CyclicBarrier

Answer: C

3. Which of the following methods does not belong to the CountDownLatch class?

A: await()
B: countDown()
C: getCount()
D: release()

Answer: D
Issue resolution: release() is the release permission method of Semaphore, which is not included in CountDownLatch class.

4. What is the difference between cyclicbarrier and CountDownLatch?

A: CyclicBarrier and CountDownLatch are essentially implemented by volatile and CAS. The differences between them are as follows:

  • CountDownLatch can only be used once, while CyclicBarrier can be used more than once.
  • CountDownLatch manually specifies to wait for one or more threads to complete execution before execution, while CyclicBarrier means that n threads wait for each other. Before any thread completes, all threads must wait.

5. Which of the following classes does not contain the await() method?

A: Semaphore
B: CountDownLatch
C: CyclicBarrier

Answer: A

6. How long did the following procedure take?

Semaphore semaphore = new Semaphore(2);
ThreadPoolExecutor semaphoreThread = new ThreadPoolExecutor(10, 50, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
for (int i = 0; i < 3; i++) {
    semaphoreThread.execute(() -> {
        try {
            semaphore.release();
            System.out.println("Hello");
            TimeUnit.SECONDS.sleep(2);
            semaphore.acquire();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    });
}

A: Within 1s
B: More than 2s

Answer: A
Problem resolution: the loop first executes release(), which is the method of releasing the license. Therefore, the program can execute three threads at a time, and at the same time, it will be completed within 1s.

7. What are the common methods of semaphore?

A: the common methods are as follows:

  • acquire(): obtain a license.
  • release(): release a license.
  • Availablelimits(): the number of licenses currently available.
  • acquire(int n): obtain and use n licenses.
  • release(int n): release n licenses.

8. What are the common methods of phaser?

A: the common methods are as follows:

  • register(): register new participants to phase
  • arriveAndAwaitAdvance(): waiting for other threads to execute
  • arriveAndDeregister(): unregister this thread
  • forceTermination(): force Phaser to enter termination state
  • isTerminated(): judge whether the phase is terminated

9. Can the following procedures be executed normally? How many times has "departure" been printed?

import java.util.concurrent.*;
public class TestMain {
    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(4, new Runnable() {
            @Override
            public void run() {
                System.out.println("Departure.");
            }
        });
        for (int i = 0; i < 4; i++) {
            new Thread(new CyclicWorker(cyclicBarrier)).start();
        }
    }
    static class CyclicWorker implements Runnable {
        private CyclicBarrier cyclicBarrier;

        CyclicWorker(CyclicBarrier cyclicBarrier) {
            this.cyclicBarrier = cyclicBarrier;
        }
        @Override
        public void run() {
            for (int i = 0; i < 2; i++) {
                System.out.println("Passenger:" + i);
                try {
                    cyclicBarrier.await();
                    System.out.println("Passenger II: " + i);
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

A: it can be executed normally. Because await() has been executed twice, the "departure" has been printed four times.

summary

In this paper, we introduce four more advanced thread synchronization classes than synchronized. The functions of CountDownLatch, CyclicBarrier and Phaser are similar, but they have different emphases. CountDownLatch is generally used to wait for one or more threads to execute before executing the current thread, and CountDownLatch cannot be reused ; CyclicBarrier is used to wait for a group of thread resources to enter the barrier point and execute together; phase is a more powerful and flexible thread assistant tool provided by JDK 7. After waiting for all threads to arrive, continue or start a new set of tasks. Phase provides the function of dynamically increasing and eliminating thread synchronization. Semaphore, on the other hand, provides more like a lock to control access to a set of resources.

Welcome to my public address, reply to the keyword "Java", there will be a gift!!! I wish you a successful interview!!!

%97%E5%8F%B7%E4%BA%8C%E7%BB%B4%E7%A0%81.png)

Topics: Java JDK