What is concurrent collaboration in concurrent programming series?

What is concurrent collaboration in concurrent programming series?

1. What is concurrent collaboration?

Multiple threads are concurrent and cooperate to complete a task. Because of the need of task processing, some threads need to be controlled to wait for other threads to complete some parts of the task, and then continue to execute.

2. Concurrent collaboration implementation

  1. wait notify notifyAll monitor method based on synchronized and Object
  2. Wait for notification method of await singal method based on Lock and Condition
  3. Based on other collaborative APIs provided in the Java concurrency package, such as CountDownLatch mode

3. Overview of JUC concurrent collaboration tool classes

The juc package of jdk provides tools for handling 1 concurrent collaboration, including CountDownLatch, CyclicBarrier, Phaser and Semaphore

4. CountDownLatch countdown latch

  • CountDownLatch countdown latch Purpose: it is used to control one or more threads to wait for a group of operations executed in other threads to complete, and then continue to execute
  • CountDownLatch usage
    • Construction method: CountDownLatch(int count). Count specifies the number of waiting conditions (tasks and operands). It cannot be changed
    • Waiting method: await(), block the waiting thread until count is reduced to 0. When count is 0, it will not block and continue to execute
    • boolean await(long timeout,TimeUnit unit): the await method that can set the timeout. Returning true means waiting for the condition to arrive; false indicates that the condition arrived in the future, but timed out
    • long getCount(): get the current count value, which is often used for debugging or testing

Precautions for CountDownLatch: it can only be used once and cannot be reused. It cannot be reused after the count becomes 0

  • CountDownLatch applicable scenario
  1. Wait for multiple conditions to complete. countDownLatch(N) can be: wait for N threads, wait for N operations, and wait for N executions of an operation
  2. For concurrent testing, wait for multiple threads to start together

Example: wait for n threads to complete execution, and then execute together

import java.util.Random;
import java.util.concurrent.CountDownLatch;

public class CountDownLatchExample {

    public static void main(String[] args) {

        final CountDownLatch cdl = new CountDownLatch(1);

        int concurrency = 100;
        final Random random = new Random();
        for (int i = 0; i < concurrency; i++) {
            new Thread(()->{
                try {
                    Thread.sleep(random.nextInt(10_000));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "Ready");
                // Let concurrent threads wait for signals
                try {
                    cdl.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "start-up");
            }).start();
        }
        System.out.println("******************** Send start signal***********");
        cdl.countDown();
    }
}

After execution, it is found that the results do not meet our requirements, although multiple threads wait and execute disorderly together:

******************** Send start signal***********
Thread-22 Ready
Thread-22 start-up
Thread-45 Ready
Thread-45 start-up
...

Because CountDownLatch cannot be reused, a new CountDownLatch is added to collaborate with N threads:

import java.util.Random;
import java.util.concurrent.CountDownLatch;

public class StartTogerCountdownLatchExample {

    public static void main(String[] args) throws InterruptedException {
        final CountDownLatch cdl = new CountDownLatch(1);
        int concurrency = 100;
        final CountDownLatch cdln = new CountDownLatch(concurrency);
        final Random random = new Random();
        for (int i = 0;i < concurrency; i++) {
            new Thread(()->{
                try {
                    Thread.sleep(random.nextInt(10_000));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(
                        Thread.currentThread().getName() + " Ready");
                // Call countDown() to report the completion of the task
                cdln.countDown();
                // Let all threads wait for a signal
                try {
                    cdl.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(
                        Thread.currentThread().getName() + " start-up");
            }).start();
        }
        //Wait for preparation to complete
        cdln.await();
        System.out.println("******************** Send start signal***********");
        cdl.countDown();
    }
}

Wait for N threads to be ready, then a total CountDownLatch sends a semaphore, and all threads execute together

...
Thread-11 Ready
Thread-14 Ready
Thread-53 Ready
Thread-91 Ready
******************** Send start signal***********
Thread-97 start-up
Thread-57 start-up
...

5. CyclicBarrier cycle barrier

  • CyclicBarrier cycle barrier definition Definition: cooperate with a specified number of threads to make these threads wait in front of the barrier until all threads reach the barrier, and then continue to execute together. After thread execution, this barrier can be used again, so it is called loop barrier.
  • Usage and principle of CyclicBarrier
    • Construction method, CyclicBarrier(int parties): parties specifies how many parts (threads) participate, which is called the number of participants.
    • Construction method, CyclicBarrier(int parties,Runnable barrierAction): barrierAction, a command that is executed once when all participants reach the barrier. After the last thread in a set of threads arrives (but before releasing all threads), the change command is executed in that thread, which is run only once at each barrier point. This barrier is useful if you want to update the shared state before proceeding with all threads.
    • int await() throws InterruptedException,BrowkenBarrierException: the thread execution process will call await() method to indicate that it has reached the barrier. The thread will block itself and wait for other threads to reach the barrier; When all threads reach the barrier, that is, the number of threads waiting is equal to the number of participants, all threads are released to continue execution. The return value int indicates the index number of the current thread. Note that the index number is reduced from parties-1 to 0. Broken barrierexception: the barrier is broken. When await is called or the barrier is broken during waiting, a broken barrierexception will be thrown.
    • Int await (long timeout, timeunit) throws interruptedexception, brokenbarrierexception, TimeoutException: wait for a specified length of time. If it cannot be released at the time, TimeoutException will be thrown
    • int getNumberWaiting(): gets the number of threads currently at the barrier
    • boolean isBroken(): judge whether the barrier is broken
    • void reset(): reset the barrier to the initialization state. If there are currently threads waiting, they will be released and a BrokenBarrierException will be thrown
  • Precautions for using CyclicBarrier
    • Be sure to have enough participant threads, otherwise they will always block at the barrier.
    • When using in the thread pool, pay attention to ensure that the number of threads in the thread pool is greater than or equal to the number of participants.
  • Applicable scenarios of CyclicBarrier
    • Threads waiting to execute together
    • Wait multiple times to execute together
  • Comparison between CountDownLatch and CyclicBarrier
    • CountDownLatch is that some threads wait for another thread to wake up
    • CyclicBarrier is a process in which participating threads wait for each other to arrive and execute together
    • CountDownLatch cannot be referenced circularly, and CyclicBarrier can be used circularly
  • Scenario: multi-stage waiting and starting together

Case: the company organizes weekend tourism activities. Everyone sets out from home to the company. After everyone arrives, they set out to the company to play, then gather at the gate of the park, and then eat in the restaurant. When everyone arrives, they begin to eat. Simulation scenarios are not programmed using.

The participants remained unchanged and waited for each other many times. The cyclic use feature of CyclicBarrier is just available

import java.util.Random;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierExample {

    public static void main(String[] args) {
        int concurrency = 100;
        final CyclicBarrier cyclicBarrier = new CyclicBarrier(concurrency , ()->{
            System.out.println("*****************Ready to finish!************");
        });
        final Random random = new Random();
        for (int i = 0 ; i < concurrency; i++) {
            new Thread(() -> {
                try {
                    Thread.sleep(random.nextInt(10_000));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.println(Thread.currentThread().getName() + "Ready");
                try {
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
                System.out.println(
                        Thread.currentThread().getName() + " start-up....");
            }).start();
        }
    }
}

Console printing:

... 
Thread-12 Ready
Thread-58 Ready
Thread-75 Ready
Thread-25 Ready
*****************Ready to finish!************
Thread-25 start-up....
Thread-89 start-up....
Thread-34 start-up....
...

6. Phaser

jdk7 adds a tool class for multi-stage synchronization control, which contains the related functions of CyclicBarrier and CountDownLatch, which are more powerful and flexible than them.

According to the understanding of Phaser phase coordinator, Phaser is applicable to tasks cooperated by multiple threads. It is divided into multiple phases. Each phase can have any participant, and threads can register and participate in a phase at any time; When all tasks in a phase are successfully completed, Phaser onAdvance() is called, and then Phaser releases the waiting thread and automatically enters the next phase. This cycle continues until the Phaser no longer contains any participants.

Phaser API Description:

  • Construction method
    • Phaser(): number of participating tasks 0
    • Phase (int parties): Specifies the initial number of participating tasks
    • Phaser(Phaser parent): Specifies the parent phaser. The child object is added to the parent object as a whole. When there is no participant in the child object, it will be deregistered from the parent object automatically
    • Phase (phase parent, int parties): a method that integrates the above two methods
  • Method of increasing or decreasing the number of participating tasks
    • int register(): add a number to return the current phase number
    • int bulkRegister(int parties): increase the specified number and return the current phase number
    • int arriveAndDeregister(): reduce the number of tasks and return the current phase number
  • Arrival waiting method
    • int arrive(): upon arrival, the task is completed, and the current phase number is returned
    • int arriveAndAwaitAdvance(): wait for other tasks to arrive after arrival, and return the arrival phase number
    • int awaitAdvance(int phase): wait in the specified phase (it must be the current phase to be valid)
    • int awaitAdvanceInterruptibly(int phase)
    • int awaitAdvanceInterruptibly(int phase , long timeout, TimeUnit unit)
  • Phase arrival trigger action
    • Protected Boolean onadvance (int phase, int registeredparties): similar to the trigger command of CyclicBarrier, the phase arrival action is added by rewriting this method
  • Other APIs
    • void forceTermination(): forced termination
    • boolean isTerMinated(): judge whether to end
    • void getPhase(): get the current phase number
  • Note: the maximum number of registered tasks allowed for a single Phaser instance is 65535. If the number of participating tasks exceeds, the parent-child Phaser tree can be used

7. Semaphore count semaphore

  • Semaphore count semaphore definition Understanding: to give a token pool, you can obtain semaphores (tokens or licenses) and put semaphores into it. It is often used to control the number of concurrent threads, and can also be used for access control of pool resources.
import java.util.Random;
import java.util.concurrent.Phaser;

public class MultipleStartTogetherPhserDemo {

	Random rd = new Random();
	int bound = 5000;

	public void step1Task() throws InterruptedException {
		// After a period of time, arrive at the company
		Thread.sleep(rd.nextInt(bound));
		System.out.println(
				"Staff[" + Thread.currentThread().getName() + "]" + "Arrive at the company!");
	}

	public void step2Task() throws InterruptedException {
		System.out.println(
				"Staff[" + Thread.currentThread().getName() + "]" + "Go to the park...");
		// After playing for some time, gather at the gate of the park
		Thread.sleep(rd.nextInt(bound));
		System.out.println(
				"Staff[" + Thread.currentThread().getName() + "]" + "Finish the park!");
	}

	public void step3Task() throws InterruptedException {
		System.out.println(
				"Staff[" + Thread.currentThread().getName() + "]" + "Go to the restaurant......");
		// After playing for some time, gather at the gate of the park
		Thread.sleep(rd.nextInt(bound));
		System.out.println(
				"Staff[" + Thread.currentThread().getName() + "]" + "Get to the restaurant!");
	}

	public void step4Task() throws InterruptedException {
		System.out.println(
				"Staff[" + Thread.currentThread().getName() + "]" + "Start eating......");
		// After playing for some time, gather at the gate of the park
		Thread.sleep(rd.nextInt(bound));
		System.out.println(
				"Staff[" + Thread.currentThread().getName() + "]" + "go back home");
	}

	public static void main(String[] args) {

		// Create a phase coordinator object, override onAdvance method, and add phase arrival processing logic
		final Phaser ph = new Phaser() {
			protected boolean onAdvance(int phase, int registeredParties) {
				int staffs = registeredParties - 1;
				switch (phase) {
				case 0:
					System.out.println("Everyone has arrived at the company. Let's go to the park! Number:" + staffs);
					break;
				case 1:
					System.out.println("Everybody go to the park gate and go to the restaurant! Number:" + staffs);
					break;
				case 2:
					System.out.println("Everyone has arrived at the restaurant and started to eat! Number of people:" + staffs);
					break;
				}
				// Judge whether there is only one participant left in the main thread. If yes, return true and the stage coordinator terminates.
				return registeredParties == 1;
			}
		};

		// Increase the number of tasks to allow the main thread to participate in the whole process
		ph.register();

		final MultipleStartTogetherPhserDemo job = new MultipleStartTogetherPhserDemo();

		// Let 3 threads participate in the whole process
		for (int i = 0; i < 3; i++) {
			// Increase the number of participating tasks
			ph.register();
			new Thread(new Runnable() {
				@Override
				public void run() {
					try {
						job.step1Task();
						ph.arriveAndAwaitAdvance();
						job.step2Task();
						System.out.println(
								"Staff[" + Thread.currentThread().getName() + "]"
										+ "Assemble at the park gate.");
						ph.arriveAndAwaitAdvance();
						job.step3Task();
						ph.arriveAndAwaitAdvance();
						job.step4Task();
						// Done, sign out and leave
						ph.arriveAndDeregister();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}).start();
		}

		// Let two employees who don't attend the dinner join
		for (int i = 0; i < 2; i++) {
			// Increase the number of participating tasks
			ph.register();
			new Thread(new Runnable() {
				@Override
				public void run() {
					try {
						job.step1Task();
						ph.arriveAndAwaitAdvance();
						job.step2Task();
						System.out.println(
								"Staff[" + Thread.currentThread().getName() + "]"
										+ "go back home");
						// Done, sign out and leave
						ph.arriveAndDeregister();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}).start();
		}

		while (!ph.isTerminated()) {
			int phaser = ph.arriveAndAwaitAdvance();

			if (phaser == 2) { // At the stage of going to the restaurant, let people who only participate in the evening dinner join
				for (int i = 0; i < 4; i++) {
					// Increase the number of participating tasks
					ph.register();
					new Thread(new Runnable() {
						@Override
						public void run() {
							try {
								job.step3Task();
								ph.arriveAndAwaitAdvance();
								job.step4Task();
								// Done, sign out and leave
								ph.arriveAndDeregister();
							} catch (InterruptedException e) {
								e.printStackTrace();
							}
						}
					}).start();
				}
			}

		}

	}
}

8. Concurrent collaboration tool class induction

Posted by dhorn on Tue, 30 Nov 2021 04:03:02 -0800