preface

Recently in some JUC source code, more aware of want to learn Java multithreading, the foundation is the key, such as want to learn ReentranLock source code, you have to master AQS source code, and AQS source code and there are a lot of Java multithreading classic some applications; Today, the Java wait/notification mode is actually the key to the implementation of Thread.join(), as well as the core of the communication between threads in the Thread pool. So in order to deepen the understanding, do this record!

What is the wait/notification mode for Java threads

1.1 Overview of wait/Notification Mode

First, an official introduction:

The wait/notification mechanism refers to that thread A invokes the wait() method of object to enter the wait state, while thread B invokes the notify or notifyAll() method of object. After receiving the notification, thread A returns from the wait() method of object O and performs subsequent operations.

My understanding is (to illustrate) :

Suppose there are two assembly lines in the factory, and the two lines are A and B for A certain work process. A is responsible for preparing various accessories, and B is responsible for renting assembly parts and then output to the workbench. B’s work requires A’s accessories to be fully prepared, otherwise, IT will wait for A to get the accessories ready. After A gets the accessories ready, IT will inform B that I am ready through an initial notice. You don’t need to wait for A long time and can continue to perform the task. Flow A and flow B are the corresponding thread A and thread B communication, that is, can be understood as cooperation, specifically is the “” notification/wait” mechanism!

1.2 Details to be paid attention to

The superclass Object has wait() and notify()/notifyAll() methods. We should understand these three methods and give some details before we start code examples. Some details are easy to ignore.

  • Calling wait() releases the lock (as I think most people know), and the thread status changes from RUNNING->WAITNG. The current thread enters the object waiting queue.

  • Calling notify()/notifyAll() does not release the lock immediately (I should all know that, but when?). ——– see next), notify() is used to move the threads in the wait queue to the synchronous queue, and notifyAll() is used to move all the threads in the wait queue to the synchronous queue. The state of the removed thread is WAITING–>BLOCKED;

  • If the notify()/notifyAll() thread releases the lock, the wait thread has a chance to return. —— Go on to the next one)

  • To return from wait(), the call object lock must be acquired. That is, after notify() and notifyAll() release the lock, the wait() becomes BLOCKED. If other threads compete for the lock, the wait thread continues to compete for the lock.

  • To use wait(), notify(), and notifyAll() methods, you need to call the object lock first. (This is probably the easiest thing to overlook. Wait (), notify(), notifyAll() —-

Code examples

2.1 Code understanding

In combination with the above “example of factory process assembling parts and producing parts”, we have two threads (pipeline) WaitThread and NotifyThread, wherein WaitThread is the notified task, which completes the main work (assembling parts and completing products) and needs to judge the flag bit (switch) at any time. NotifyThread is a task that needs to be notified. It is necessary to “supervise notification” WaitThread. The combination of the two can better complete product assembly and output.

public class WaitNotify { static Object lock = new Object(); static boolean flag = false; public static void main(String[] args) { new Thread(new WaitThread(), "WaitThread").start(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(new NotifyThread(), "NotifyThread").start(); } /** * line A, Static class thread implements Runnable{@override public void run() {synchronized (lock){static class thread implements Runnable{@override public void run() { // If the condition is not met, wait for another thread to change the condition and notify the wait thread while (! flag){ try { System.out.println(Thread.currentThread() + " is waiting, flag is "+flag); // The wait() method call releases the lock and the current thread enters the wait queue. lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); System.out.println(thread.currentThread () + "is running, flag is "+flag); }}} /** * line B, switch control, A */ static class NotifyThread implements Runnable{@override public void run() {// Obtain the same object lock as the wait thread synchronized (lock){ flag = true; // Notify the wait thread that I have changed the condition and you can continue to execute (while continuing after returning) // Notify notify() and wait until the current thread releases the lock I need to inform all assembly lines A..... lock.notifyAll(); System.out.println(Thread.currentThread() + " hold lock, notify waitThread and flag is "+flag); }}}}Copy the code

Run the main function and output:

Thread[WaitThread,5,main] is waiting, flag is false
Thread[NotifyThread,5,main] hold lock, notify waitThread and flag is true
Thread[WaitThread,5,main] is running, flag is true
Copy the code

When the lathe flow work is started, the switch of the flow line is closed at the beginning (flag=false). When line B (NotifyThread) is switched off, it automatically wakes up line A (WaitThread), and the whole flow line starts to work……

  • Thread[WaitThread,5,main] is waiting, flag is false At the beginning, assembly line A found that it had no spare parts to assemble, so it waited for assembly line A to prepare spare parts (doesn’t that feel very silly, ha ha ha, real assembly line doesn’t waste time, and there will be many assembly line B to prepare spare parts, here is just an example, please understand!) ;

  • Thread[NotifyThread,5,main] Hold lock, notify waitThread and flag is true: Pipeline B prepares the accessories, turns on the switch (flag= true), and notifies pipeline A to start working.

  • Thread[WaitThread,5,main] is running and flag is true. Pipeline B receives the notification and checks whether the switch is turned on. If it is turned on, it starts to return to finish the work.

The above examples make sense. Here is a rough sequence diagram:

—-wait() returns only if the lock is obtained

The details of this note have been expressed above: the prerequisite for returning from wait() is to acquire the call object lock, and we add a synchronized block (the scarlet letter part) that can compete for the lock.

public class WaitNotify { static Object lock = new Object(); static boolean flag = false; public static void main(String[] args) { new Thread(new WaitThread(), "WaitThread").start(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(new NotifyThread(), "NotifyThread").start(); } /** * line A, Static class thread implements Runnable{@override public void run() {synchronized (lock){static class thread implements Runnable{@override public void run() { // If the condition is not met, wait for another thread to change the condition and notify the wait thread while (! flag){ try { System.out.println(Thread.currentThread() + " is waiting, flag is "+flag); // The wait() method call releases the lock and the current thread enters the wait queue. lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); System.out.println(thread.currentThread () + "is running, flag is "+flag); }}} /** * line B, switch control, A */ static class NotifyThread implements Runnable{@override public void run() {// Obtain the same object lock as the wait thread synchronized (lock){ flag = true; // Notify the wait thread that I have changed the condition and you can continue to execute (while continuing after returning) // Notify notify() and wait until the current thread releases the lock I need to inform all assembly lines A..... lock.notifyAll(); System.out.println(Thread.currentThread() + " hold lock, notify waitThread and flag is "+flag); } synchronized (system.out.println (thread.currentThread () + "hold lock again"); }}}}Copy the code

Output result:

Thread[WaitThread,5,main] is waiting, flag is false
Thread[NotifyThread,5,main] hold lock, notify waitThread and flag is true
Thread[NotifyThread,5,main] hold lock again
Thread[WaitThread,5,main] is running, flag is true
Copy the code

The third and fourth may be in reverse order, because the lock may be contorted by synchronized blocks in the scarlet, or it may be contorted by WaitThreads and returned from the wait().

Thread[WaitThread,5,main] is waiting, flag is false
Thread[NotifyThread,5,main] hold lock, notify waitThread and flag is true
Thread[WaitThread,5,main] is running, flag is true
Thread[NotifyThread,5,main] hold lock again
Copy the code

Application of wait/notification mode

3.1 Application of source code in thread.join (

Thread. The join () function: When thread A waits for thread to terminate before returning from thread.join(), each thread terminates only when the precursor thread terminates, and each thread waits for the precursor thread to terminate before returning from join. This involves A wait/notification mechanism (waiting for the precursor thread to terminate and receiving notification of the precursor thread’s termination).

In thread.join (), use while to check whether the Thread is alive or not. If the Thread is still alive, wait until the Thread is alive.

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"); } while(){wait(millis)} {if (millis == 0) {// While (millis == 0) { While (isAlive()) {// wait wait wait(0); } else {while (isAlive()) {long delay = millis-now; if (delay <= 0) { break; } wait(delay); now = System.currentTimeMillis() - base; }}}Copy the code

3.2 Other applications

The essence of a thread pool is to use a thread-safe work queue to connect worker threads and client threads, with the client thread putting work on the work queue and returning, and the worker thread continually fetching work from the work queue and executing it. So, the wait/notification pattern applied here is:

If there is no thread job in the work queue, that is, if the task force column is empty, wait for the client to put the work queue thread task, and notify the worker thread to continue to fetch thread execution from the work queue.

Note: on the thread pool application source code is not introduced here, because it is not finished (I have not fully digested), first a brief introduction to the application of the place and the concept.

Note: Database connection pooling is similar to thread pooling and also involves wait/notification.

3.3 Wait/notify paradigm

With so many applications introduced, there should be a uniform paradigm for this pattern. Yes, there must be:

For those waiting (also known as consumers) :

Synchronized (object lock) {while (condition not satisfied) {object.wait (); } // TODO processing logic}Copy the code

For notifiers (also known as producers) :

Synchronized (object lock) {while (condition satisfied) {change condition object. Notify (); }}Copy the code

Note: In practice, it is best to use the timeout wait/notification pattern, which is perfectly embodied in the thread.join() source method

Wait (), notify(), notifyAll() —- to prevent threads from starving

(1) To release a lock, wait() to release a lock.

(2) Wait () must be used with notify() or notifyAll(), otherwise the thread will be WAITING until it calls Object.wait () without timeout and notifyAll(). The thread that calls wait() is always hungry.

(3) Due to article 2, we know that: Even if we use notify() or notifyAll() to wake up a thread but do not wake up at an appropriate time (such as before calling wait()), the wait() thread is still WAITING, so we must ensure that the wait() method is either not executed. Or it’s done before it’s woken up. Synchronized (” wait() “) {notify/notifyAll (); synchronized (” wait() “);

Synchronized (object lock) {while (condition not satisfied) {// 1 If notify/notifyAll is executed, the thread is WAITING for the object after 2 is executed. // 2}Copy the code

Illustrating the order of execution is:

(4) Synchronized code blocks can automatically release locks if a thread terminates due to code errors or other reasons, and wait() is not executed.

The wait/notify mode introduced here is actually implemented with Object monitor methods (wait(), notify(), etc.) and Synchronized.

  • Condition works with the wait/notification pattern implemented by Lock

  • LockSupport block park and wake up unpark