preface

Happy New Year’s Day 2018.

Abstract:

  1. How to use Notify Wait?
  2. Why must it be in a synchronized block?
  3. Implement a simple producer-consumer model using Notify Wait
  4. Underlying Implementation Principles

1. How to use Notify Wait?

Today we are going to learn or analyze the Object class wait notify two methods, in fact, two methods, including their overloaded methods altogether 5, and the Object class only 12 methods, so we can see the importance of these two methods. Let’s start with the code in the JDK:

public final native void notify(a);

public final native void notifyAll(a);
 
public final void wait(a) throws InterruptedException {
    wait(0);
}

public final native void wait(long timeout) throws InterruptedException;

public final void wait(long timeout, int nanos) throws InterruptedException {
    if (timeout < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }

    if (nanos < 0 || nanos > 999999) {
        throw new IllegalArgumentException(
                            "nanosecond timeout value out of range");
    }

    if (nanos > 0) {
        timeout++;
    }

    wait(timeout);
}
Copy the code

Those are the five ways. Three of these methods are native, meaning they are executed by the VIRTUAL machine’s native C code. Two wait overloading methods ended up calling wait (long) anyway.

First, know how. Here’s a simple example of how to use both methods.

package cn.think.in.java.two;

import java.util.concurrent.TimeUnit;

public class WaitNotify {

  final static Object lock = new Object();

  public static void main(String[] args) {

    new Thread(new Runnable() {
      @Override
      public void run(a) {
        System.out.println("Thread A is waiting to pick up the lock");
        synchronized (lock) {
          try {
            System.out.println("Thread A got the lock.");
            TimeUnit.SECONDS.sleep(1);
            System.out.println("Thread A starts to wait and waives the lock.");
            lock.wait();
            System.out.println("Continue to run to the end when informed to continue.");
          } catch (InterruptedException e) {
          }
        }
      }
    }, "Thread A").start();

    new Thread(new Runnable() {
      @Override
      public void run(a) {
        System.out.println("Thread B waits for lock");
        synchronized (lock) {
          System.out.println("Thread B got the lock.");
          try {
            TimeUnit.SECONDS.sleep(5);
          } catch (InterruptedException e) {
          }
          lock.notify();
          System.out.println("Thread B randomly notifies a thread of the Lock object."); }}},"Thread B").start(); }}Copy the code

Running results:

Thread A Lock with thread B waiting for thread A Lock to Lock the thread A begin to wait and give up thread Lock to Lock the thread B B random notice A thread Lock objects Be informed can continue continues to run to the end

In the above code, thread A and thread B both grab the lock. Thread A gets the lock first, calls wait, waives the lock, and suspends itself. Thread B then gets the lock and notifies thread A. After the notification, thread B did not execute the code in the synchronized code block, so A could not get the lock, so it could not run, wait until thread B completed the execution, out of the synchronized block, this time thread A was activated to continue to execute.

Two unrelated threads can communicate with each other using wait and notify. This is often referred to in interview questions as how threads communicate with each other.

Without wait and noitfy, how do we get two threads to communicate? A simple way to do this is to have a thread loop through a tag variable, such as:

while(the value! = flag) {thread.sleep (1000);
}
doSomeing();
Copy the code

The above code will sleep for a period of time if the condition is not met. The purpose of this code is to prevent too fast “invalid attempts”. This method seems to do the desired function, but it has the following problems:

  1. Difficulty in ensuring timeliness. Because waiting 1000 times will cause a time difference.
  2. It is difficult to reduce overhead, and if timeliness is ensured, sleep time is shortened, which can greatly consume CPU.

But with Java’s built-in wait and notify methods, everything is solved. Officially, it’s a wait/notification mechanism. When a thread is waiting, another thread can notify this thread, realizing the communication between threads.

2. Why must it be in a synchronized block?

Note that the two methods must be used in the Synchroized synchronized block, and that in the synchronized block of the current object, if the wait or notify methods of B are called in the A method, Virtual opportunity thrown IllegalMonitorStateException, illegal monitor is unusual, because you this thread holds the monitor and the monitor you call is not an object.

So why do these two methods have to be in a synchronized block?

Here’s a technical term: race conditions. What are competitive conditions?

When two threads compete for the same resource, a race condition is said to exist if the order in which the resources are accessed is sensitive.

Race conditions can lead to bugs in concurrent situations. Race conditions can occur when multiple threads compete for resources, and if the program to be executed first fails to compete and is executed later, the entire program will have some uncertain bugs. Such bugs are difficult to find and can be repeated because threads compete randomly.

Suppose you have two threads, a producer and a consumer, each with a task.

1.1 Producer check condition (such as cache is full) -> 1.2 Producer must wait 2.1 Consumer consumes a unit of cache -> 2.2 Resets the condition (such as cache is not full) -> 2.3 Call notifyAll() to wake producer

We want the order to be 1.1->1.2->2.1->2.2->2.3, but since CPU execution is random, 2.3 May be executed first and 1.2 later, which will cause the producer to never wake up!

So we have to manage the process, namely synchronization, and by combining wait and notify in a synchronized block, we can manually adjust the order of execution of the threads.

3. Implement a simple producer-consumer model using Notify Wait

While many books don’t recommend using Notify and WAIT directly for concurrent programming, it’s still important to learn. A simple example of a producer and consumer:

Simple caching classes:


public class Queue {

  final int num;
  final List<String> list;
  boolean isFull = false;
  boolean isEmpty = true;


  public Queue(int num) {
    this.num = num;
    this.list = new ArrayList<>();
  }


  public synchronized void put(String value) {
    try {
      if (isFull) {
        System.out.println("PutThread paused, relinquished the lock.");
        this.wait();
        System.out.println("PutThread woke up and got the lock.");
      }

      list.add(value);
      System.out.println("PutThread is in." + value);
      if (list.size() >= num) {
        isFull = true;
      }
      if (isEmpty) {
        isEmpty = false;
        System.out.println("PutThread getThread notice");
        this.notify(); }}catch(InterruptedException e) { e.printStackTrace(); }}public synchronized String get(int index) {
    try {
      if (isEmpty) {
        System.err.println("GetThread paused and relinquished the lock");
        this.wait();
        System.err.println("GetThread woke up and got the lock.");
      }

      String value = list.get(index);
      System.err.println("GetThread got it" + value);
      list.remove(index);

      Random random = new Random();
      int randomInt = random.nextInt(5);
      if (randomInt == 1) {
        System.err.println("Random number equals 1. Clear the set.");
        list.clear();
      }

      if (getSize() < num) {
        if (getSize() == 0) {
          isEmpty = true;
        }
        if (isFull) {
          isFull = false;
          System.err.println("GetThread notifies putThread that it can be added.");
          Thread.sleep(10);
          this.notify(); }}return value;


    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    return null;
  }


  public int getSize(a) {
    return list.size();
  }


Copy the code

Producer thread:

class PutThread implements Runnable {

  Queue queue;

  public PutThread(Queue queue) {
    this.queue = queue;
  }

  @Override
  public void run(a) {
    int i = 0;
    for(; ;) { i++; queue.put(i +"No."); }}}Copy the code

Consumer thread:

class GetThread implements Runnable {

  Queue queue;

  public GetThread(Queue queue) {
    this.queue = queue;
  }

  @Override
  public void run(a) {
    for(; ;) {for (int i = 0; i < queue.getSize(); i++) {
        try {
          Thread.sleep(1000);
        } catch(InterruptedException e) { e.printStackTrace(); } String value = queue.get(i); }}}}Copy the code

In fact, the JDK’s internal blocking queue is similar to this implementation, but instead of synchronized, it uses reentrant locking.

Basically, the classic producer-consumer model has the following rules:

The waiting party follows the following rules:

  1. Gets the lock of the object.
  2. If the condition is not met, the object’s wait method is called and the condition is checked even after being notified.
  3. If the conditions are met, the corresponding logic is executed.

The corresponding pseudocode is listed below:

{synchroize (objects)while{object. Wait (); } Corresponding processing logic...... }Copy the code

The notifying party follows the following rules:

  1. Get the lock of the object.
  2. Change the conditions.
  3. Notifies all threads waiting on an object.

The corresponding pseudocode is as follows:

synchronized(object) {change the condition object. NotifyAll (); }Copy the code

4. Underlying implementation principles

If you know how to use it, you have to know how it works, right?

So first of all, what’s the general order in which we use these two methods?

  1. To use Wait, notify, and notifyAll, lock the calling object first.
  2. After the wait method is called, the thread state changes from Running to Waiting and the current thread is placed on the object’s wait queue.
  3. After notify or notifyAll is called, the wait thread still does not return from wait. The wait thread has to release the lock after noitfy is called.
  4. The notify method moves one thread of the wait queue from the wait queue to the synchronous queue, while the notifyAll method moves all the threads of the wait queue to the synchronous queue, and the status of the moved thread changes from Waiting to Blocked.
  5. The return from the wait method is conditional upon obtaining the lock of the calling object.

As you can see from the above details, the wait/notification mechanism relies on the synchronization mechanism to ensure that the waiting thread is aware of changes made to variables by the notification thread when it returns from the wait method.

The diagram depicts the steps above:

WaitThread acquires the lock, invokes the wait method, and enters the wait queue. NotifyThread acquires the lock, invokes notify, and moves the WatiThread to the synchronization queue. NotifyThread completes execution, releases the lock, and WaitThread acquires the lock again and returns from wait to continue execution.

Here, about the application layer of wait and notify basically about the same, the following is about the virtual machine level, involving Java built-in lock implementation, synchronized keyword implementation, JVM source code. This is an extension of this article.

The current thread must own this object’s Monitor. The current thread must own this object’s Monitor.

If we compile the code with the synchronized keyword, we’ll see that a section of code is enclosed by monitorenter and Monitorexit directives, which is what synchronized does during compilation. Then, when the bytecode is executed, The c code corresponding to this instruction will be executed. Here, we must stop. The principles of synchronized start to get involved, and this article won’t discuss them.

Wait Noitfy’s answers are all in the C code of the Java HotSpot virtual machine. But R told us not to read the virtual machine source code easily, many details may cover up the abstract, resulting in learning efficiency is not high. If you are interested, there are 3 articles written specifically to parse the source code from HotSpot.

Java wait(), notify() 和 Java wait() 和 Java wait() 和 Java wait() 和 Java wait() 和 Java wait() 和 Java wait() 和 Java wait() 和 Java wait() 和 Java wait() 和 Java wait() 和 Java wait() 和 Java wait() 和 Java wait() 和 Java wait() 和 Java wait() 和 Java Modified JVM source control grab lock sequence, and Wolfman JVM source analysis Object. Wait /notify implementation.

The implementation principles of Wait and Notify are explained from the JVM source code level in the above four articles.

gleanings

  1. Wait (long) method, which takes milliseconds, that is, the thread is automatically returned if it has waited a specified number of milliseconds.
  2. Wait (long, int) method, which increments the nanosecond level by adding the first millisecond to the next nanosecond.
  3. After the notify method is called, if there are many threads waiting, the JDK source code will randomly search for one, but the JVM source code actually looks for the first one.
  4. NotifyAll and notify do not take effect immediately and must wait until the caller has relinquished the lock after executing the synchronized block.

conclusion

Ok, so that’s it for the use and rationale of Wait Noitfy. I don’t know if you noticed that concurrency is highly related to virtual machines. Therefore, it can be said that the process of learning concurrency is the process of learning virtual machines. And read the openJDK code in the virtual machine let head big, but anyway, ugly daughter-in-law sooner or later see her in-laws, openJDK code is must see, come on !!!!