Java中Object对象wait/notify/notifyAll方法详细解析

:capo 转载请注明原创出处,谢谢!

前言:

今天,我们讲讲Object中wait和notify/notifyAll这一组方法,我们来看看JDK中关于这两个方法的说明:

/**    
       引起当前线程等待直到另一个线程调用当前对象的notify方法或notify()方法或者一些其他的线程中断当前线程,或者一个指定的时间已经过去
     * Causes the current thread to wait until another thread invokes the
     * {@link java.lang.Object#notify()} method or the
     * {@link java.lang.Object#notifyAll()} method for this object, or
     * some other thread interrupts the current thread, or a certain
     * amount of real time has elapsed.
     * 

* This method is similar to the {@code wait} method of one * argument, but it allows finer control over the amount of time to * wait for a notification before giving up. The amount of real time, * measured in nanoseconds, is given by: *

*
     * 1000000*timeout+nanos
*

* In all other respects, this method does the same thing as the * method {@link #wait(long)} of one argument. In particular, * {@code wait(0, 0)} means the same thing as {@code wait(0)}. *

* The current thread must own this object's monitor. The thread * releases ownership of this monitor and waits until either of the * following two conditions has occurred: *

    *
  • Another thread notifies threads waiting on this object's monitor * to wake up either through a call to the {@code notify} method * or the {@code notifyAll} method. *
  • The timeout period, specified by {@code timeout} * milliseconds plus {@code nanos} nanoseconds arguments, has * elapsed. *
*

* The thread then waits until it can re-obtain ownership of the * monitor and resumes execution. *

* As in the one argument version, interrupts and spurious wakeups are * possible, and this method should always be used in a loop: *

     *     synchronized (obj) {
     *         while ()
     *             obj.wait(timeout, nanos);
     *         ... // Perform action appropriate to condition
     *     }
     * 
* This method should only be called by a thread that is the owner * of this object's monitor. See the {@code notify} method for a * description of the ways in which a thread can become the owner of * a monitor. * * @param timeout the maximum time to wait in milliseconds. * @param nanos additional time, in nanoseconds range * 0-999999. * @throws IllegalArgumentException if the value of timeout is * negative or the value of nanos is * not in the range 0-999999. * @throws IllegalMonitorStateException if the current thread is not * the owner of this object's monitor. * @throws InterruptedException if any thread interrupted the * current thread before or while the current thread * was waiting for a notification. The interrupted * status of the current thread is cleared when * this exception is thrown. */ 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); } /** * Wakes up a single thread that is waiting on this object's * monitor. If any threads are waiting on this object, one of them * is chosen to be awakened. The choice is arbitrary and occurs at * the discretion of the implementation. A thread waits on an object's * monitor by calling one of the {@code wait} methods. *

* The awakened thread will not be able to proceed until the current * thread relinquishes the lock on this object. The awakened thread will * compete in the usual manner with any other threads that might be * actively competing to synchronize on this object; for example, the * awakened thread enjoys no reliable privilege or disadvantage in being * the next thread to lock this object. *

* This method should only be called by a thread that is the owner * of this object's monitor. A thread becomes the owner of the * object's monitor in one of three ways: *

    *
  • By executing a synchronized instance method of that object. *
  • By executing the body of a {@code synchronized} statement * that synchronizes on the object. *
  • For objects of type {@code Class,} by executing a * synchronized static method of that class. *
*

* Only one thread at a time can own an object's monitor. * * @throws IllegalMonitorStateException if the current thread is not * the owner of this object's monitor. * @see java.lang.Object#notifyAll() * @see java.lang.Object#wait() */ public final native void notify();

我总结了一下关于这个方法使用注意事项:

  • 引起当前线程等待,直到另一个线程调用此对象的notify()方法或notifyAll()方法.或者指定线程超时等待一定时间后。
  • 这个超时时间单位是纳秒,其计算公式为: 1000000*timeout+nanos
  • 如果使用wait(0)和wait(0,0)是等价的
  • 如果当前对象在没有获得锁的监视器的情况下就调用wait或者notify/notifyAll方法就是抛出IllegalMonitorStateException异常
  • 当前对象的wait方法会暂时释放掉对象监视器的锁,所以wait必须是在synchronized同步块中使用,因为synchronized同步块进入是默认是要获取对象监视器的。同理notify/notifyAll操作也要在对象获取监视器的情况下去唤醒一个等待池中的线程
  • wait操作还要在一个循环中使用,防止虚假唤醒

wait/notify在工作中的应用,等待通知机制(消费者-生产者模式)

一个线程修改了一个对象的值,而另一个线程感知道了变化,然后进行相应的操作,整个过程开始于一个线程,而最终执行又是另一个线程。前者是生产者,后者是消费者。接下来我们使用wait/notify实现这个机制

package com.minglangx.object;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
* 

  * @ClassName: WaitNotify

  * @Description: 使用wait/notify实现等待通知机制

  * @author minglangx

  * @date 2017年9月4日 下午4:16:30

  *


  
*/
public class WaitNotify {
 
 public static boolean flag = true;
 public static Object lock = new Object();
 
 
 
 public static void main(String[] args){
     
     Thread waitTHread = new Thread(new Wait(),"WaitThread");
     waitTHread.start();
     try {
         Thread.sleep(1);
     } catch (InterruptedException e) {
         e.printStackTrace();
     }
     
     Thread notifyThread = new Thread(new Notify(),"NotifyThread");
     notifyThread.start();
     
 }
 
 
 static class Wait implements Runnable{
     
     @Override
     public void run() {
         
         //获取 lock对象监视器 并加锁
         synchronized (lock) {
             //当条件不满足时,继续wait,同时只是暂时释放了lock对象上的锁,并将当前对象防止到对象的等待队列中
             while(flag) {
                 try {
                     
                     System.out.println(Thread.currentThread() 
                             + "flag is true. wait@ " 
                             + new SimpleDateFormat("HH:mm:ss")
                             .format(new Date()));
                     
                     lock.wait();
                     
                     
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
                 
                 
             }
             
             //当条件满足时,完成工作
             System.out.println(Thread.currentThread() 
                     + "flag is true. wait@ " 
                     + new SimpleDateFormat("HH:mm:ss")
                     .format(new Date()));
             
             
         }
         
         
     }
     
     
     
 }
 
 static class Notify implements Runnable{
     
     @Override
     public void run() {
         /*
          * 获取对象的监视器
          */
         synchronized (lock) {
             //获取对象上的锁,然后通知等待队列中的所有对象,但这个时候不会释放锁
             System.out.println(Thread.currentThread()
                      + " 持有锁..notify @" 
                      + new SimpleDateFormat("HH:mm:ss").format(new Date()));
             
             //调用该方法后,将会把所有等待队列中的线程全部移动到同步队列中
             lock.notifyAll();
             //将条件置为 false
             flag = false;
             try {
                 Thread.sleep(5);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
             
             
             
         }
         
         
         //再次加锁
         synchronized (lock) {
             System.out.println(Thread.currentThread()
                      + " 再次持有锁..sleep @" 
                      + new SimpleDateFormat("HH:mm:ss").format(new Date()));
             
             try {
                 Thread.sleep(5);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
             
         }
         
     }
     
     
 }
 

}

这段代码最后输出:

Java中Object对象wait/notify/notifyAll方法详细解析_第1张图片
image.png

我们看到当调用notify并没有释放掉对象上的锁,而是要等待synchronized代码块走完在释放掉对象上的锁

这段代码向我们说明了几点

  • 调用wait()方法后,线程状态由 running 变为等待wait, 并将当前线程放入等待队列
  • notify、notifyAll方法调用后,等待线程依旧不会从wait()返回,需要调用notify()或者notifyAll()的线程释放掉锁后,等待线程才有机会从wait()返回
  • notify()方法是将等待队列中一个等待线程从等待队列移动到同步队列中,而notifyAll则是将所有等待队列中的线程移动到同步队列中,被移动的线程状态由 running变为 阻塞blocked

为此我们规范一下这个等待、通知机制(消费者,生产者模式)如何编写
等待者(消费者)
编写代码步骤:

  1. 获取对象上的锁
  2. 如果条件不满足,则调用对象上的wait()方法,应该使用一个while()条件判断
  3. 条件满足则执行对应的业务逻辑
    其中伪代码:
    synchronized(对象) {
    while(条件不满足){
    对象.wait();
    }
    处理对应的业务逻辑
    }

通知者(生产者)
编写代码步骤:
1) 获取对象上的锁

  1. 改变条件
  2. 通知所有(一个)等待在对象上的线程
    对应的伪代码:
    synchronized(对象) {
    改变条件
    对象.notifyAll();
    }

总结:

  • 使用wait或者notify()方法一定要在同步代码块中使用,而wait一般要在while循环中使用
  • wait/notify可以实现生产者消费者模式,其原理是调用wait时将线程放入等待队列,而调用notify时将等待队列中的线程移动到同步队列
  • wait/notify机制是成对出现的,它们的实现依赖于锁的同步机制

你可能感兴趣的:(Java中Object对象wait/notify/notifyAll方法详细解析)