美文网首页
Java中Object对象wait/notify/notifyA

Java中Object对象wait/notify/notifyA

作者: capo | 来源:发表于2017-09-05 17:21 被阅读212次

    简书: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.
         * <p>
         
         * 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:
         * <blockquote>
         * <pre>
         * 1000000*timeout+nanos</pre></blockquote>
         * <p>
         * 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)}.
         * <p>
         * 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:
         * <ul>
         * <li>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.
         * <li>The timeout period, specified by {@code timeout}
         *     milliseconds plus {@code nanos} nanoseconds arguments, has
         *     elapsed.
         * </ul>
         * <p>
         * The thread then waits until it can re-obtain ownership of the
         * monitor and resumes execution.
         * <p>
         * As in the one argument version, interrupts and spurious wakeups are
         * possible, and this method should always be used in a loop:
         * <pre>
         *     synchronized (obj) {
         *         while (<condition does not hold>)
         *             obj.wait(timeout, nanos);
         *         ... // Perform action appropriate to condition
         *     }
         * </pre>
         * 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 <i>interrupted
         *             status</i> 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.
         * <p>
         * 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.
         * <p>
         * 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:
         * <ul>
         * <li>By executing a synchronized instance method of that object.
         * <li>By executing the body of a {@code synchronized} statement
         *     that synchronizes on the object.
         * <li>For objects of type {@code Class,} by executing a
         *     synchronized static method of that class.
         * </ul>
         * <p>
         * 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();
                 }
                 
             }
             
         }
         
         
     }
     
    
    }
    

    这段代码最后输出:

    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/notifyA

          本文链接:https://www.haomeiwen.com/subject/iqxpjxtx.html