概述
在Java中, 我们可以使用
wait()wait(long timeout)wait(long timeout, int nanos)notify()notifyAll()
这5个方法来实现同步代码块之间的通信, 注意, 我说的是同步代码块之间的通信, 这意味着:
调用该方法的当前线程必须持有对象的监视器锁
(源码注释: The current thread must own this object's monitor.)
其实, 这句话换个通俗点的说法就是: 只能在同步代码块中使用这些方法.
道理很简单, 因为只有进入了同步代块, 才能获得监视器锁.
wait方法的作用是, 阻塞当前线程(阻塞的原因常常是一些必要的条件还没有满足), 让出监视器锁, 不再参与锁竞争, 直到其他线程来通知(告知必要的条件已经满足了), 或者直到设定的超时等待时间到了.
notify和notifyAll方法的作用是, 通知那些调用了wait方法的线程, 让它们从wait处返回.
可见, wait 和 notify 方法一般是成对使用的, 我把它简单的总结为:
等通知
wait 是等, notify 是通知.
为了给大家一个感性的认识, 我这里打个比方:
假设你和舍友一起租了个两室一厅一厨一卫的房子, 天这么热, 当然每天都要洗澡啦, 但是卫生间只有一个, 同一时间, 只有一个人能用.
这时候, 你先下班回来了, 准备要洗澡, 刚进浴室, 突然想起来你的专用防脱洗发膏用完了, 查了下快递说是1小时后才能送到, 但这时候你的舍友回来了, 他也要洗澡, 所以你总不能"站着茅坑不拉屎"吧, 所以你主动让出了浴室(调用wait方法, 让出监视器锁), 让舍友先洗, 自己等快递.
过了一个小时, 快递送来了你的防脱洗发膏(调用了nofity方法, 唤醒在wait中的线程), 你现在需要洗澡的资源都有了, 万事俱备, 就差进入浴室了, 这个时候你去浴室门口一看, 嘿, 浴室空着!(当前没有线程占用监视器锁) 舍友已经洗好了! 于是你高高兴兴的带着你的防脱洗发水进去洗澡了(再次获得监视器锁).
当然, 上面还有另外一种情况, 假如你不知道快递员什么时候会来, 可能在一小时后, 也可能是明天, 那总不能一直干等着不洗澡吧, 于是你决定, 我就等一个小时(调用带超时时间的wait(long timeout)方法), 一小时后快递还不来, 就不等了, 大不了用沐浴露凑合着洗洗头 o(TヘTo)
上面只是拿生活中的例子打了个比方, 不知道大家理解了没有, 下面我们就来正经的看看代码.
源码分析
以上5个都方法定义在了java的Object类中, 这意味着java中所有的类都会继承这些方法.
同时, 下面的源码分析中我们将看到, 这些方法都是final类型的, 也就是说所有的子类都不能改写这些方法.
下面我们来看源码:
(这一段会比较长, 不想看源码分析的可以直接跳过这一部分看结论)
wait方法
public final void wait() throws InterruptedException {
wait(0);
}
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);
}
public final native void wait(long timeout) throws InterruptedException;
wait方法共有三个, 我们发现, 前两个方法都是调用了最后一个方法, 而最后一个方法是一个native方法.
我们知道, native方法是非java代码实现的, 我们看不到它的具体实现内容, 但是java规定了该方法要实现什么样的功能, 即它应该在java代码里"看起来是什么样子的".
所以native方法就像java的接口一样, 但是具体实现由JVM直接提供,或者(更多情况下)由外部的动态链接库(external dynamic link library)提供,然后被JVM调用。
在Object的源码的注释中, 描述了该native方法"看起来应该是什么样子的", 我们一段一段来看:
(这里我把原文也贴出来了, 是怕自己翻译的不够精确, 英语好的可以直接看原文)
/**
* 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>
...
*/
这段是说, 该方法导致了当前线程挂起, 直到其他线程调用了这个object的 notify或者notifyAll方法, 或者设置的超时时间到了(超时时间即timeout参数的值, 以毫秒为单位), 另外它提到了, 当前线程必须已经拿到了监视器锁, 这点我们在开篇的概论中已经提到了.
/*
...
* 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.
...
*/
这段话的大意是说, 该方法使得当前线程进入当前监视器锁(this object)的等待队列中(wait set), 并且放弃一切已经拥有的(这个监视器锁上)的同步资源, 然后挂起当前线程, 直到以下四个条件之一发生:
- 其他线程调用了
this object的notify方法, 并且当前线程恰好是被选中来唤醒的那一个(下面分析notify的时候我们就会知道, 该方法会随机选择一个线程去唤醒) - 其他线程调用了
this object的notifyAll方法, - 其他线程中断了(interrupt)了当前线程
- 指定的超时时间到了.(如果指定的时间是0, 则该线程会一直等待, 直到收到其他线程的通知)
这里插一句, 关于第四条, 解释了无参的wait方法:
public final void wait() throws InterruptedException {
wait(0);
}
我们知道, 无参的wait方法的超时时间就是0, 也就是说他会无限期等待, 直到其他线程调用了notify
或者notifyAll.
同时, 我们再看另一个有两个参数的wait方法:
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);
}
这个方法在其源码的注释中号称是实现了纳秒级别的更精细的控制:
/*
*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>
...
*/
但是我们实际看源码可知, 当nanos的值大于0但低于999999时, 即低于1毫秒时, 就直接将timeout++了, 所以这里哪里来的纳秒级别的控制??? 最后不还是以毫秒为粒度吗? 不过是多加一毫秒而已. 这个方法真的不是在卖萌吗?(  ̄ー ̄)
注意, 这里同样说明了 wait(0,0) 与 wait(0)是等效的, 这点其实直接将值代入源码也能得出这个结论.
好了, 吐槽完毕, 我们接着看剩下来的注释:
/*
...
* 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.
...
*/
这一段说的就是满足了上面四个条件之一之后的事情了, 此时该线程会从wait set中移除, 重新参与到线程调度中, 并且和其他线程一样, 竞争锁资源, 一旦它又获得了监视器锁, 则它在调用wait方法时的所有状态都会被恢复, 即我们熟知的恢复现场.
/*
...
* <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 (<condition does not hold>)
* 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).
...
*/
这一段是说即使没有满足上面4个条件之一, 线程也可能被唤醒, 称之为假唤醒, 虽然这种情况很少出现, 但是作者建议我们将wait放在循环体中, 并且检测唤醒条件是不是真的满足了, 并且还:
推荐了两本书...
推荐了两本书...
推荐了两本书...
还愣着干嘛, 赶紧去买书呀(~ ̄(OO) ̄)ブ
/*
...
* <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.
...
*/
这段解释了中断部分, 说的是当前线程在进入wait set之前或者在wait set之中时, 如果被其他线程中断了, 则会抛出InterruptedException异常, 但是, 如果是在恢复现场的过程中被中断了, 则直到现场恢复完成后才会抛出InterruptedException(这段不知道我理解的对不对, 因为对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.
*/
这段话的意思是说, 即使wait方法把当前线程放入this object的wait set里, 也只会释放当前监视器锁(this object), 如果当前线程还持有了其他同步资源, 则即使当前线程被挂起了, 也不会释放这些资源.
同时, 这里也提到, 该方法只能被已经持有了监视器锁的线程所调用.
到这里, wait方法我们就分析完了, 虽然它是一个native方法, 源码中并没有具体实现, 但是java规定了该方法的行为, 这些都体现了源码的注释中了.
同时, 我们的分析中多次出现了 monitor, this object, wait set等术语, 这些概念涉及到wait方法的实现细节, 我们后面会讲.
notify & notifyAll
notify和notifyAll方法都是native方法:
public final native void notify();
public final native void notifyAll();
相比于wait方法, 这两个方法的源码注释要少一点, 我们就不分段看了, 直接看全部的
notify
/**
* 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()
*/
上面这段是说:
-
notify方法会在所有等待监视器锁的线程中任意选一个唤醒, 具体唤醒哪一个, 交由该方法的实现者自己决定.
-
被唤醒的线程只有等到当前持有锁的线程完全释放了锁才能继续.(这里解释下, 因为调用
notify方法时, 线程还在同步代码块里面, 只有离开了同步代码块, 锁才会被释放) -
被唤醒的线程和其他所有竞争这个监视器锁的线程地位是一样的, 既不享有优先权, 也不占劣势.
-
这个方法应当只被持有监视器锁的线程调用, 一个线程可以通过以下三种方法之一获得
this object的监视器锁:- 通过执行该对象的普通同步方法
- 通过执行
synchonized代码块, 该代码块以this object作为锁 - 通过执行该类的静态同步方法
我们通过上一篇介绍synchronized同步代码块的文章知道, synchronized作用于类的静态方法时, 是拿类的Class对象作为锁, 作用于类的普通方法或者 synchronized(this){}代码块时, 是拿当前类的实例对象作为监视器锁, 这里的this object, 指的应该是该线程调用notify方法所持有的锁对象.
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.
*
* @throws IllegalMonitorStateException if the current thread is not
* the owner of this object's monitor.
* @see java.lang.Object#notify()
* @see java.lang.Object#wait()
*/
上面这段是说: notifyAll方法会唤醒所有等待this object监视器锁的线程, 其他内容和notify一致.
总结
总则: 调用这5个方法的线程必须持有监视器锁。
-
wait方法会使当前线程进入自己所持有的监视器锁(
this object)的等待队列中, 并且放弃一切已经拥有的(这个监视器锁上的)同步资源, 然后挂起当前线程, 直到以下四个条件之一发生:- 其他线程调用了
this object的notify方法, 并且当前线程恰好是被选中来唤醒的那一个 - 其他线程调用了
this object的notifyAll方法, - 其他线程中断了当前线程
- 指定的超时时间到了.(如果指定的超时时间是0, 则该线程会一直等待, 直到收到其他线程的通知)
- 其他线程调用了
-
当以上4个条件之一满足后, 该线程从
wait set中移除, 重新参与到线程调度中, 并且和其他线程一样, 竞争锁资源, 一旦它又获得了监视器锁, 则它在调用wait方法时的所有状态都会被恢复, 这里要注意“假唤醒”的问题. -
当前线程在进入
wait set之前或者在wait set之中时, 如果被其他线程中断了, 则会抛出InterruptedException异常, 但是, 如果是在恢复现场的过程中被中断了, 则直到现场恢复完成后才会抛出InterruptedException -
即使wait方法把当前线程放入
this object的wait set里, 也只会释放当前监视器锁(this object), 如果当前线程还持有了其他同步资源, 则即使它在this object中的等待队列中, 也不会释放. -
notify方法会在所有等待监视器锁的线程中任意选一个唤醒, 具体唤醒哪一个, 交由该方法的实现者自己决定.
-
线程调用notify方法后不会立即释放监视器锁,只有退出同步代码块后,才会释放锁(与之相对,调用wait方法会立即释放监视器锁)
-
线程被notify或notifyAll唤醒后会继续和其他普通线程一样竞争锁资源