The world has kissed me with its pain asking for its return in song — Stray Birds Tagore

Although usually each child thread only needs to complete its own task, sometimes we want multiple threads to work together to complete a task, which involves interthread communication.

Methods and classes involved in interthread communication include: Thread.join (), object.wait(), Object.notify (), CountdownLatch, CyclicBarrier, FutureTask, Callable.

Here are a few examples of how to implement interthread communication in Java:

  1. How do I get two threads to execute sequentially, i.e. one thread waits for the other to complete before executing?
  2. How do I get two threads to intersect in order in the specified way?
  3. There are four threads: A, B, C, and D. How can D execute after A, B, and C are all executed synchronously?
  4. The three runners prepare separately and then start running at the same time when everyone is ready.
  5. When the child thread completes the task, it returns the result to the main thread.

1. How do I get two threads to execute in sequence?

Suppose you have two threads, A and B, both of which can print numbers in order as follows:

public class Test01 {

    public static void main(String[] args) throws InterruptedException {
        demo1();
    }

    public static void demo1(a) {
        Thread a = new Thread(() -> {
            printNumber("A");
        });

        Thread b = new Thread(() -> {
            printNumber("B");
        });

        a.start();
        b.start();
    }

    public static void printNumber(String threadName) {
        int i = 0;
        while (i++ < 3) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(threadName + " print: "+ i); }}}Copy the code

The results are as follows:

A print: 1
B print: 1
B print: 2
A print: 2
A print: 3
B print: 3
Copy the code

If we want B to start executing after A completes, we can use the thread.join() method as follows:

public static void demo2(a) {
    Thread a = new Thread(() -> {
        printNumber("A");
    });

    Thread b = new Thread(() -> {
        System.out.println("B waits for A to execute");
        try {
            a.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        printNumber("B");
    });

    a.start();
    b.start();
}

Copy the code

The results are as follows:

A print:1
A print: 2
A print: 3
B print: 1
B print: 2
B print: 3
Copy the code

We can see that the a.jin () method makes B wait for A to finish printing.

The purpose of the thread.join() method is to block the current thread until the thread calling the join() method completes execution.

The join() method is called as follows:

public final void join(a) throws InterruptedException {
    join(0);
}
Copy the code

Join (0) :

// Note that the sychronized lock is used. The lock object is an instance object of the thread
public final synchronized void join(long millis) throws InterruptedException {
    long base = System.currentTimeMillis();
    long now = 0;

    if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }
	// Call join(0) to execute the following code
    if (millis == 0) {
        // The purpose of the while loop is to avoid false awakenings
        // If the current thread is alive, wait(0) is called. 0 means wait permanently until notifyAll() or notify() is called
        // notifyAll() is called when the thread terminates
        while (isAlive()) {
            wait(0); }}else {
        while (isAlive()) {
            long delay = millis - now;
            if (delay <= 0) {
                break; } wait(delay); now = System.currentTimeMillis() - base; }}}Copy the code

The join(long millis) method is implemented through the wait(Long Timeout) method. Before calling the wait method, the current thread must acquire the lock of the Object. So this join method uses synchronized locking, which is an instance object of the thread. Wait (0) blocks the current thread until another thread calls notify() or notifyAll(). NotifyAll () is called when the thread calling join terminates, so join() allows one thread to wait until another thread calling join() terminates.

False wake up: a thread is woken up without notification, interruption, or timeout.

False wake up may lead to the execution of the code when the condition is not valid and destroy the constraint relationship protected by the lock;

Why use a while loop to avoid false awakenings:

Using the wait method in an if block is dangerous because once the thread is woken up and locked, it will no longer execute code outside the if block. Therefore, it is recommended to use the while loop whenever the condition is checked and then wait. The loop tests the condition before and after the wait.

2. How to make two threads intersect in order in the specified way?

If we now want thread B to print 1, 2, and 3 immediately after thread A prints 1, and then thread A continues to print 2,3, we need more fine-grained locks to control the order of execution.

Here we can use the object.wait() and object.notify() methods as follows:

public static void demo3(a) {
    Object lock = new Object();
    Thread A = new Thread(() -> {
        synchronized (lock) {
            System.out.println("A 1");
            try {
                lock.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("A 2");
            System.out.println("A 3"); }}); Thread B =new Thread(() -> {
        synchronized (lock) {
            System.out.println("B 1");
            System.out.println("B 2");
            System.out.println("B 3"); lock.notify(); }}); A.start(); B.start(); }Copy the code

The results are as follows:

A 1
B 1
B 2
B 3
A 2
A 3
Copy the code

The execution flow of the above code is as follows:

  1. First we create an object lock shared by A and B:lock = new Object();
  2. When A gets the lock, it prints 1 and then callslock.wait()Method enters the wait state and surrenders control of the lock;
  3. B is not executed until A calls itlock.wait()Method releases control and B acquires the lock;
  4. B gets the lock, prints 1,2,3, and callslock.notify()Method to wake up waiting A;
  5. Continue printing the remaining 2,3 after A wakes up.

To make it easier to understand, I have added the above code to the log as follows:

public static void demo3(a) {
    Object lock = new Object();
    Thread A = new Thread(() -> {
        System.out.println("INFO: A waiting to acquire lock");
        synchronized (lock) {
            System.out.println("INFO: A has obtained lock");
            System.out.println("A 1");
            try {
                System.out.println(INFO: A enters waiting state and gives up control of lock);
                lock.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("INFO: A is awakened by B to continue execution");
            System.out.println("A 2");
            System.out.println("A 3"); }}); Thread B =new Thread(() -> {
        System.out.println("INFO: B waiting to acquire lock");
        synchronized (lock) {
            System.out.println("INFO: B obtains lock");
            System.out.println("B 1");
            System.out.println("B 2");
            System.out.println("B 3");
            System.out.println("INFO: B finishes execution, call notify to wake up A"); lock.notify(); }}); A.start(); B.start(); }Copy the code

The results are as follows:

INFO: A Waiting to obtain lock INFO: A obtains lock A1INFO: A is in waiting state and has relinquished the lock control INFO: B is waiting to obtain the lock INFO: B has obtained lock B1
B 2
B 3INFO: B invokes the notify method to wake up A. INFO: A is woken up by B and continues to perform A2
A 3
Copy the code

3. Thread D is executed after A, B, and C are synchronized

The method thread.join() described earlier allows one thread to continue execution while waiting for another thread to finish. But if we add A, B, and C to thread D, we will have A, B, and C running in sequence, and we want all three to run in sync.

The goal we want to achieve is that threads A, B and C can start running at the same time and notify D when they are finished independently. D will not run until A, B, and C are all running. So we use CountdownLatch for this type of communication. Its basic usage is:

  1. Create a counter and set an initial value,CountdownLatch countDownLatch = new CountDownLatch(3);
  2. callcountDownLatch.await()Enter the wait state until the count becomes 0;
  3. Called in another threadcountDownLatch.countDown(), the method will subtract the calculated value by one;
  4. When the value of the counter becomes0When,countDownLatch.await()The method in the waiting thread continues to execute the following code.

The implementation code is as follows:

public static void runDAfterABC(a) {
    int count = 3;
    CountDownLatch countDownLatch = new CountDownLatch(count);
    new Thread(() -> {
        System.out.println("INFO: D waits for A, B and C to complete");
        try {
            countDownLatch.await();
            System.out.println("INFO: A, B, C completed, D started");
            System.out.println("D is working");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }).start();

    for (char threadName = 'A'; threadName <= 'C' ; threadName++) {
        final String name = String.valueOf(threadName);
        new Thread(() -> {
            System.out.println(name + " is working");
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(name + " finished"); countDownLatch.countDown(); }).start(); }}Copy the code

The results are as follows:

INFO: D Waiting for A B C to complete A is working B is working C is working C finished B finished A finished INFO: A, B, C, D is workingCopy the code

The CountDownLatch itself is a countdown counter, and we set the initial count value to 3. When D runs, the countdownlatch.await () method is first called to check if the counter value is 0, and if it is not, the wait state is kept. After A, B, and C have run, use countdownlatch.countdown () to decrease the countDown counter by 1, respectively. The counter is decayed to 0, and the await() method is notified to stop waiting and D continues.

Therefore, CountDownLatch is suitable for situations where one thread needs to wait on multiple threads.

The three runners separated to start at the same time

This time, the three threads A, B, AND C need to be prepared separately and run at the same time after the three threads are ready. How should we do this?

CountDownLatch can be used to count, but only one await() method of one thread will get the response when the count is complete, so multiple threads cannot be triggered at the same time. To achieve the effect of threads waiting for each other, we can use this CyclicBarrier, which is basically:

  1. Start by creating a public objectCyclicBarrierAnd set the number of threads waiting at the same time.CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
  2. These threads start preparing at the same time, and when ready, need to wait for someone else to be ready, so callcyclicBarrier.await()Method to wait for others;
  3. This is called when the specified thread needs to wait simultaneouslycyclicBarrier.await()Method, which means the threads are ready, and the threads will start executing simultaneously.

Imagine that there are three runners who need to start running at the same time, so they need to wait for everyone else to get ready. The implementation code is as follows:

public static void runABCWhenAllReady(a) {
    int count = 3;
    CyclicBarrier cyclicBarrier = new CyclicBarrier(count);
    Random random = new Random();
    for (char threadName = 'A'; threadName <= 'C' ; threadName++) {
        final String name = String.valueOf(threadName);
        new Thread(() -> {
            int prepareTime = random.nextInt(10000);
            System.out.println(name + "Preparation time:" + prepareTime);
            try {
                Thread.sleep(prepareTime);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(name + "Ready and waiting for the others.");
            try {
                cyclicBarrier.await();
            } catch (InterruptedException | BrokenBarrierException e) {
                e.printStackTrace();
            }
            System.out.println(name + "Start running"); }).start(); }}Copy the code

The results are as follows:

A Preparation time:1085B Preparation time:7729C Preparation time:8444A is ready, waiting for the others B is ready, waiting for the others C is ready, waiting for the others C starts running A starts running B starts runningCopy the code

A CyclicBarrier waits for multiple threads to execute simultaneously.

5. The child thread returns the result to the main thread

In real development, we often need to create child threads to do time-consuming tasks and then pass the execution results back to the main thread. So how do you do that in Java?

When creating a Thread, we pass the Runnable object to the Thread.

@FunctionalInterface
public interface Runnable {
    public abstract void run(a);
}

Copy the code

As you can see, Runable is a functional interface. The run method in this interface does not return a value, so if you want to return a result, you can use another similar interface, Callable.

Functional interface: Interface that has only one method

Callable interface source code is as follows:

@FunctionalInterface
public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call(a) throws Exception;
}
Copy the code

As you can see, the biggest difference with Callable is that it returns a generic type.

So the next question is, how do you get the result of the child thread back? Java has a class, FutureTask, that works with Callable, but note that the methods get uses to get results block the main thread. FutureTask is also a Runnable by nature, so it can be passed directly to threads.

For example, if we want the child thread to calculate the sum from 1 to 100 and return the result to the main thread, the code looks like this:

public static void getResultInWorker(a) {
    Callable<Integer> callable = () -> {
        System.out.println("Subtask starts execution");
        Thread.sleep(1000);
        int result = 0;
        for (int i = 0; i <= 100; i++) {
            result += i;
        }
        System.out.println("Subtask completes and returns result");
        return result;
    };
    FutureTask<Integer> futureTask = new FutureTask<>(callable);
    new Thread(futureTask).start();

    try {
        System.out.println("Start futureTask.get()");
        Integer result = futureTask.get();
        System.out.println("Results of execution:" + result);
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch(ExecutionException e) { e.printStackTrace(); }}Copy the code

The results are as follows:

Start futureTask.get() subtask start subtask finish and return result result of execution:5050
Copy the code

You can see that the main thread blocks when calling futureTask.get(); Callable then starts executing internally and returns the result of the operation; Futuretask.get () then gets the result and the main thread resumes running.

As you can see here, FutureTask and Callable can get the results of child threads directly in the main thread, but they block the main thread. Of course, if you don’t want to block the main thread, consider using the ExecutorService to manage execution of FutureTasks into a thread pool.

Reference article:

www.tutorialdocs.com/article/jav…