Wait+Sleep+notify(详细底层源码分析)

一、前言

首先要明确,wait是Object类的方法,也就是说,所有的对象都有wait方法,而且都是Object中的wait方法因为wait方法被标为final无法被重写,源码如下:

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

native关键字修饰,表示这个方法使用其他语言实现,又由java由C编写可得,这个方法做了很可能是C与操作系统级别的交互。抛出InterruptedException,表示线程可能由已经终止的风险。

Object提供了几个wait方法:
在这里插入图片描述

二、wait()源码分析

  /**
     * 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.
     * In other words, this method behaves exactly as if it simply
     * performs the call {@code wait(0)}.
     * <p>
     * The current thread must own this object's monitor. The thread
     * releases ownership of this monitor and waits until 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 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 (&lt;condition does not hold&gt;)
     *             obj.wait();
     *         ... // 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.
     */
    public final void wait() throws InterruptedException {
    
    
        wait(0);
    }

注释分析: 一个线程采用wait()会导致当前线程等待,直到另一个线程调用notify()或者notifyAll()[注意是调用这个对象的notify()]当前的线程必须要拥有该对象的锁,线程调用wait()方法后会[释放对这个锁的所有权],并等待直到另一个线程通过调用notify方法或notifyAll方法去将在该对象的锁上等待的线程唤醒。然后线程等待直到可以重新获得那个锁的所有权并恢复执行。
然后此方法应总是在循环中使用:(范例)

 synchronized (obj) {
    
    
            while (&lt;condition does not hold&gt;)  //条件尚未满足
            obj.wait();
           ... // Perform action appropriate to condition
        }

对比Sleep

 /**
     * Causes the currently executing thread to sleep (temporarily cease
     * execution) for the specified number of milliseconds, subject to
     * the precision and accuracy of system timers and schedulers. The thread
     * does not lose ownership of any monitors.
 **/
    
    public static native void sleep(long millis) throws InterruptedException;

使当前正在执行的线程进入睡眠状态(暂时停止执行)达指定的毫秒数,这取决于系统计时器和调度程序的精度和准确性。线程不会失去该对象的锁的所有权。

三、wait(long timeout)源码分析

参数就是用来作 超时处理

/**
     * Causes the current thread to wait until either another thread invokes the
     * {@link java.lang.Object#notify()} method or the
     * {@link java.lang.Object#notifyAll()} method for this object, or a
     * specified amount of time has elapsed.
     * <p>
     * The current thread must own this object's monitor.
     * <p>
     * This method causes the current thread (call it <var>T</var>) to
     * place itself in the wait set for this object and then to relinquish
     * any and all synchronization claims on this object. Thread <var>T</var>
     * becomes disabled for thread scheduling purposes and lies dormant
     * until one of four things happens:
     * <ul>
     * <li>Some other thread invokes the {@code notify} method for this
     * object and thread <var>T</var> happens to be arbitrarily chosen as
     * the thread to be awakened.
     * <li>Some other thread invokes the {@code notifyAll} method for this
     * object.
     * <li>Some other thread {@linkplain Thread#interrupt() interrupts}
     * thread <var>T</var>.
     * <li>The specified amount of real time has elapsed, more or less.  If
     * {@code timeout} is zero, however, then real time is not taken into
     * consideration and the thread simply waits until notified.
     * </ul>
     * The thread <var>T</var> is then removed from the wait set for this
     * object and re-enabled for thread scheduling. It then competes in the
     * usual manner with other threads for the right to synchronize on the
     * object; once it has gained control of the object, all its
     * synchronization claims on the object are restored to the status quo
     * ante - that is, to the situation as of the time that the {@code wait}
     * method was invoked. Thread <var>T</var> then returns from the
     * invocation of the {@code wait} method. Thus, on return from the
     * {@code wait} method, the synchronization state of the object and of
     * thread {@code T} is exactly as it was when the {@code wait} method
     * was invoked.
     * <p>
     * A thread can also wake up without being notified, interrupted, or
     * timing out, a so-called <i>spurious wakeup</i>.  While this will rarely
     * occur in practice, applications must guard against it by testing for
     * the condition that should have caused the thread to be awakened, and
     * continuing to wait if the condition is not satisfied.  In other words,
     * waits should always occur in loops, like this one:
     * <pre>
     *     synchronized (obj) {
     *         while (&lt;condition does not hold&gt;)
     *             obj.wait(timeout);
     *         ... // Perform action appropriate to condition
     *     }
     * </pre>
     * (For more information on this topic, see Section 3.2.3 in Doug Lea's
     * "Concurrent Programming in Java (Second Edition)" (Addison-Wesley,
     * 2000), or Item 50 in Joshua Bloch's "Effective Java Programming
     * Language Guide" (Addison-Wesley, 2001).
     *
     * <p>If the current thread is {@linkplain java.lang.Thread#interrupt()
     * interrupted} by any thread before or while it is waiting, then an
     * {@code InterruptedException} is thrown.  This exception is not
     * thrown until the lock status of this object has been restored as
     * described above.
     *
     * <p>
     * Note that the {@code wait} method, as it places the current thread
     * into the wait set for this object, unlocks only this object; any
     * other objects on which the current thread may be synchronized remain
     * locked while the thread waits.
     * <p>
     * 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.
     * @throws  IllegalArgumentException      if the value of timeout is
     *               negative.
     * @throws  IllegalMonitorStateException  if the current thread is not
     *               the owner of the 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.
     * @see        java.lang.Object#notify()
     * @see        java.lang.Object#notifyAll()
     */
    public final native void wait(long timeout) throws InterruptedException;

该带参数的wait(long timeout)方法注释比较多,是因为那个不带参数的wait()是间接去调用这个带参数(传0)的方法来实现的。

**注释分析:**此方法导致当前线程将自身置于该对象的等待集中,然后放弃对该对象的所有同步声明(就是释放锁)。线程该就无法执行线程调度目的而被禁用,并且处于休眠状态直到发生以下四种情况之一:

  • 另外一个线程调用这个对象的notify()方法,然后当前的线程(处于那个集合中)碰巧被唤醒。
  • 其他的线程调用了这个对象的notifyAll()方法
  • 其他线程打断了这个线程的等待休眠状态
  • 指定的等待时间已经过去了,不过如果那个参数是0的话,则不会考虑实时,并且线程只是等待直到唤醒。

然后将这个线程从该对象的等待集合中删除,并重新启用线程调度。然后它以通常的方式与其他线程竞争在对象上进行同步的权利。一旦它获得了对象的控制权,它对对象的所有同步声明都将恢复到以前的状态,即恢复到调用 wait方法之前被调用的那时刻的情况。然后该线程从wait()方法的调用返回。那么,从wait方法返回时,对象和线程的同步状态与调用wait方法时的状态完全相同。

线程也可以唤醒,而不会被通知,中断或超时,即所谓的虚假唤醒。尽管在实践中很少会发生这种情况,但是应用程序必须通过测试可能导致线程唤醒的条件来防范它,并在条件不满足时继续等待。然后该等待总是发生在如下的循环中(范例):

 synchronized (obj) {
    
    
             while (&lt;condition does not hold&gt;)
             obj.wait(timeout);
             ... // Perform action appropriate to condition
         }

注意:请注意,wait方法将当前线程放入此对象的等待集中,因此只能解锁该对象;当线程等待时,当前线程可以在其上同步的任何其他对象保持锁定。

扫描二维码关注公众号,回复: 12562250 查看本文章
  • 这句话的意思是:一个线程在执行的时候,有可能调用好几个对象的wait方法,在某一时刻通过对象调用notify方法只会唤醒那个对象所对应在等待集合中的线程,当前线程等待着其他对象的唤醒的话,依然还是保持同步等待状态。(即notify方法只被那个持有对象的锁的线程锁所调用)

四、wait(long timeout, int nanos))源码分析

 /**
     * 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 (&lt;condition does not hold&gt;)
     *             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.
     */
    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);
    }

最终都调用了我上面贴了源码的那个方法。这里wait由两个参数的方法需要解释一下,一个long类型参数表示的就是线程等待时间,第二个int类型参数nanos表示纳秒,1秒=1毫秒=1000微秒=1000000纳秒。当参数nanos大于0时,等待的毫秒值也就是timeout++。

五、notify()方法

```java
 /**
     * 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.
     *
     */
    public final native void notify();

唤醒正在此对象的锁上等待的单个线程。如果有多个线程在等待该对象,则选择其中一个*唤醒。该选择是任意的,并且在实现时自行决定。一个线程会通过调用一个wait方法进行在该对象的锁上等待。

在当前线程放弃对该对象的锁定之前,唤醒的线程将无法继续。唤醒的线程将以通常的方式与可能正在主动竞争以在该对象上进行同步的任何其他线程竞争;例如,被唤醒的线程在成为锁定该对象的下一个线程时没有任何可靠的特权或劣势。

另外调用notify方法一定是那个持有对象的锁的线程,线程通过以下三种方式之一成为对象的锁的所有者:

  • 通过执行该对象的synchronized实例方法。
  • 通过执行在对象上synchronized的语句的主体。
  • 对于类型为Class的对象,执行该类的synchronized静态方法。

在某一刻只有一个线程拥有对象的锁。

六、notifyAll()方法源码分析

 /**
     * Wakes up all threads that are waiting on this object's monitor. A
     * thread waits on an object's monitor by calling one of the
     * {@code wait} methods.
     * <p>
     * The awakened threads will not be able to proceed until the current
     * thread relinquishes the lock on this object. The awakened threads
     * will compete in the usual manner with any other threads that might
     * be actively competing to synchronize on this object; for example,
     * the awakened threads enjoy 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. See the {@code notify} method for a
     * description of the ways in which a thread can become the owner of
     * a monitor.
     */
    public final native void notifyAll();

唤醒正在此对象的锁上等待的所有线程。 线程通过调用wait方法之一在对象的监视器上等待。 唤醒的线程将无法继续直到当前线程放弃该对象上的锁。唤醒的线程将以通常的方式与可能正在主动竞争以与此对象进行同步的任何其他线程竞争;例如,被唤醒的线程在作为锁定该对象的下一个线程时没有任何可靠的特权或劣势。 此方法只能由作为该对象的锁的所有者的线程调用。

七、总结一波notify()和notifyAll()的区别

注:(wait/notify/notifyAll)只能在取得对象锁的时候才能调用。

调用notifyAll通知所有线程继续执行,只能有一个线程在执行其余的线程在等待(因为在所有线程被唤醒的时候在synchornized块中)。这时的等待和调用notifyAll前的等待是不一样的。

  • notifyAll前:在对象上休息区内休息
  • notifyAll后:在排队等待获得对象锁。

notify和notifyAll都是把某个对象上休息区内的线程唤醒,notify只能唤醒一个,但究竟是哪一个不能确定,而notifyAll则唤醒这个对象上的休息室中所有的线程.

一般有为了安全性,我们在绝对多数时候应该使用notifiAll(),除非你明确知道只唤醒其中的一个线程。

八、小例子

在这里插入图片描述
注意static关键字:

  • 如果没有static关键字,对于那个test对象能否在没有执行完method1(),然后去执行method2呢?
    • 答案是不行的,因为对于同一个对象,一个线程执行了syjchronized的一个方法时(即已经获得该对象的锁),另外一个线程是无法再获取这个对象的锁了,所以无法执行method2()
  • 如果加上static关键字,对于method1()它对应线程的所持有的锁是test这个实例出来的对象。 而加了static的method2方法 它对应的线程持有的锁 是Test对象的class对象,因为static关键字会导致 该类在被JVM加载的时候 生成一个代表该类的java.lang.Class对象,然后对于static代码块会被该class对象预先加载到JVM的方法区。

九、notify()和notifyAll()的区别(程序演示)

代码描述:有一个Caculate类,类中又一个成员变量 j,现在有多个线程对这个变量进行操作。一个增加操作、一个减少操作。增加操作:当 j = 0 时,j++ 。减少操作:当 j = 1 时,j-- 。这两个操作分别对应这 add()方法 和sub()方法,都使用synchronized关键字。

public class ThreadTest1
{
    
    
    public static void main(String[] args)
    {
    
    
        Caculate caculate = new Caculate();
        
        //使用多个线程对实例caculate进行增加操作。
        for(int i = 0; i < 10; i++)
        {
    
    
            Thread1 t = new Thread1(caculate);
            t.start();
        }
        
        //使用多个线程对实例caculate进行减少操作。
        for(int i = 0; i < 2; i++)
        {
    
    
            Thread2 t = new Thread2(caculate);
            t.start();
        }
    }
}

//Thread1线程进行增加操作
class Thread1 extends Thread
{
    
    
    private Caculate caculate;
    
    public Thread1()
    {
    
    
        
    }    
    
    public Thread1(Caculate caculate)
    {
    
    
        this.caculate = caculate;
    }
    
    @Override
    public void run()
    {
    
    
        int i = 0;

        //死循环,手动停止
        while(true)
        {
    
    
            try
            {
    
    
                caculate.add();
            } catch (InterruptedException e)
            {
    
    
                e.printStackTrace();
            }
            i++;
            System.out.println("加线程执行第 " + i + " 次");
        }
    }    
}

//Thread2进行减少操作。
class Thread2 extends Thread
{
    
        
    private Caculate caculate;
    
    public Thread2()
    {
    
    
    }

    public Thread2(Caculate caculate)
    {
    
    
        this.caculate = caculate;
    }

    @Override
    public void run()
    {
    
        
        int i = 0;
        
        //死循环,手动停止
        while(true)
        {
    
    
            try
            {
    
    
                caculate.sub();
            } catch (InterruptedException e)
            {
    
    
                e.printStackTrace();
            }
            i++;
            System.out.println("减线程执行第 " + i + " 次");
        }
    }    
}

class Caculate
{
    
    
    private int j = 0;    
    //增加操作
    public synchronized void add() throws InterruptedException
    {
    
    
     //当 j = 1 的时候说明不符合操作条件,要放弃对象锁。
        while(j == 1)
        {
    
    
            wait();
            System.out.println();
        }    
        j++;
        System.out.println(j);
        notify();
    }

    //减少操作
    public synchronized void sub() throws InterruptedException
    {
    
    
     //当j = 0 的时候说明不符合操作条件,放弃对象锁
        while(j == 0)
        {
    
    
            wait();
            System.out.println();
        }
        j--;
        System.out.println(j);
        notify();
    }
}

以上代码并不能一直循环执行,按道理说应该是一直循环执行的。

为什么呢????????

这就涉及到了notify() 和 notifyAll()的其中一个区别了。

这个区别就是:调用 notify() 方法只能随机唤醒一个线程,调用notifyAll() 方法的唤醒所有的等待的线程。

比如这里,当一个线程在正常执行。。。假设这里正常执行完一个增加操作的线程,然后调用 notify() 方法 那么它会随机唤醒一个线程。

  • 如果唤醒的是进行减少操作的线程,此时 j = 1,线程能够正常执行减少操作。

  • 如果唤醒的是进行增加操作的线程,此时 j = 1,那么不符合增加操作的条件,他就会调用 wait()方法。那么调用完wait()方法后程序就会发现已经没有被唤醒的线程了。唯一一个被唤醒的线程因不符合条件放弃了对象锁,其他线程又没有被唤醒。此时程序只能一直等到其他线程被唤醒,但是它等不到了。

解决:

把notify() 改成notifyAll() 这个问题就解决了。因为如果唤醒一个线程,但是这个线程因不符合执行条件而放弃对象,还有很多唤醒的线程。

所以,当多个(两个以上的)线程操作同一个对象的时候最好使用的notifyAll(),这样就不会出现上述的问题了。

发现一个问题,既然使用notify()会出问题那为什么不在每个地方的使用notifyAll()呢??这二者还有其他我没了解的区别吗???难道使用notifyAll() 会使性能大大下降???

  • 可能大致是因为唤醒全部所有的等待的线程进行单独一个锁的竞争,竞争完后,那些线程还在等待,比较耗CPU资源。

猜你喜欢

转载自blog.csdn.net/weixin_42754971/article/details/113838333