Details the Wait Notify mechanism for concurrent Java programs

  • 2020-04-01 04:05:02
  • OfStack

Wait - Notify scenario
A typical wait-notify scenario is typically associated with two things:
1. State Variable
When a thread needs to wait, it is always because some condition is not met. For example, if a queue is filled with data, the thread needs to wait until the queue element is full. When there is a vacancy in the queue element, the execution continues.
2. Condition Predicate
When a thread determines whether to enter a wait or continue executing when it wakes up from a notify, most of them test whether the status condition is satisfied. For example, when an element is added to a queue, the queue is full, and the current thread is blocked. When another thread removes the element from the queue, the waiting thread is notified that "the queue has space to add the element". At this point, the process waiting to add elements will be awakened, and then determine whether the current queue really has spare space, if there is spare space, the element will be added, if not, continue to block until the next wake.
3. Condition Queue
Each object has a built-in conditional queue that adds a thread to the conditional queue of an object when it calls the wait function on the object lock.

Pay attention to
Wait and notify are important parts of the Java synchronization mechanism. Combined with the use of the synchronized keyword, many excellent synchronization models, such as the producer-consumer model, can be built. However, when using the wait(), notify(), and notifyAll() functions, you need to pay special attention to the following:

      The wait(), notify(), and notifyAll() methods do not belong to the Thread class, but to the Object base class, which means that each Object has the functions of wait(), notify(), and notifyAll(). Because every object has a lock, the lock is the basis of every object, so the method of operating the lock is also the most basic.
      Before calling obj's wait(), notify() methods, the obj lock must be acquired, i.e., must be written in the synchronized(obj){... } in the code segment.
      After calling obj.wait(), thread A releases obj's lock, otherwise thread B cannot acquire the obj lock and cannot enter the synchronized(obj){... } wake thread A in the code segment.
      When the obj.wait() method returns, thread A needs to acquire the obj lock again to continue execution.
      If threads A1,A2, and A3 are all in obj.wait(), then thread B calls obj.notify() to wake only one of the threads A1,A2,A3 (which is determined by the JVM).
      If thread B calls obj.notifyall (), it can all wake up the waiting thread A1,A2,A3, but the waiting thread must acquire the obj lock to continue with the next statement of obj.wait(). Therefore, only one of the threads A1,A2,A3 has a chance to acquire the lock to continue execution, such as A1, and the rest need to wait for A1 to release the obj lock to continue execution.
      When thread B calls obj.notify() or obj.notifyAll(), thread B is holding the obj lock, so threads A1,A2, and A3 are awakened, but cannot acquire the obj lock. It is not until thread B exits the synchronized block and releases the obj lock that one of the threads A1,A2, and A3 has a chance to acquire the object lock and continue execution.


The sample code
The typical code structure of a thread's wait operation is as follows:


  public void test() throws InterruptedException { 
    synchronized(obj) { 
      while (! contidition) { 
        obj.wait(); 
      } 
    } 
  } 

Why must the obj.wait() operation be in a loop? There are several main reasons:
1. An object lock can be used to protect multiple state variables, and when they all require a wait-notify operation, it can be a problem not to put the wait into a while loop. For example, an object lock obj protects two state variables a and b. a wait operation occurs when the condition assertion of a is not valid, and a wait operation occurs when the condition assertion of b is not valid, and two threads are added to the condition queue corresponding to obj. Now if change one operating state variable a, call on the obj notifyAll operation, the obj corresponding conditions all the threads in the queue are awakened, before waiting for a one or a few threads to judge the condition of a assertion may be formed, but b for conditional assertions must not stand still, and wait for the b threads were awakened, so you need to loop judge b conditions assertions are met, if not satisfied, and continues to wait.
2. Conditional assertion of the same state for multiple threads to wait. For example, in the case of adding elements to a queue, the current queue is full and multiple threads want to add elements to it, so they all wait. At this point, another thread takes an element out of the queue, invokes the notifyAll operation, and wakes up all the threads, but only one thread can add an element to the queue, and the others still have to wait.
False awakening. The thread automatically wakes up without being notified, interrupted, or timed out. Although this rarely happens in practice, it can be prevented by circular waiting.



Related articles: