上一篇文章咱们讲了java的同步代码块, 这一篇咱们来看看同步代码块之间的协做与通讯.java
阅读本篇前你须要知道什么是同步代码块, 什么是监视器锁, 还不是很了解的同窗建议先去看一看上一篇文章.segmentfault
本文的源码基于JDK1.8app
系列文章目录less
在Java中, 咱们可使用ide
wait()
wait(long timeout)
wait(long timeout, int nanos)
notify()
notifyAll()
这5个方法来实现同步代码块之间的通讯, 注意, 我说的是同步代码块之间的
通讯, 这意味着:oop
调用该方法的当前线程必须持有对象的监视器锁
(源码注释: The current thread must own this object's monitor.)
其实, 这句话换个通俗点的说法就是: 只能在同步代码块中使用这些方法. 源码分析
道理很简单, 由于只有进入了同步代块, 才能得到监视器锁.ui
wait
方法的做用是, 阻塞当前线程(阻塞的缘由经常是一些必要的条件尚未知足), 让出监视器锁, 再也不参与锁竞争, 直到其余线程来通知(告知必要的条件已经知足了), 或者直到设定的超时等待时间到了.this
notify
和notifyAll
方法的做用是, 通知那些调用了wait
方法的线程, 让它们从wait
处返回.线程
可见, wait
和 notify
方法通常是成对使用的, 我把它简单的总结为:
等通知
wait
是等, notify
是通知.
为了给你们一个感性的认识, 我这里打个比方:
假设你和舍友一块儿租了个两室一厅一厨一卫的房子, 天这么热, 固然天天都要洗澡啦, 可是卫生间只有一个, 同一时间, 只有一我的能用.
这时候, 你先下班回来了, 准备要洗澡, 刚进浴室, 忽然想起来你的专用防脱洗发膏用完了, 查了下快递说是1小时后才能送到, 但这时候你的舍友回来了, 他也要洗澡, 因此你总不能"站着茅坑不拉屎"吧, 因此你主动让出了
浴室(调用wait方法, 让出监视器锁), 让舍友先洗, 本身等
快递.
过了一个小时, 快递送来了你的防脱洗发膏(调用了nofity方法, 唤醒在wait中的线程), 你如今须要洗澡的资源都有了, 万事俱备, 就差进入浴室了, 这个时候你去浴室门口一看, 嘿, 浴室空着!(当前没有线程占用监视器锁) 舍友已经洗好了! 因而你高高兴兴的带着你的防脱洗发水进去洗澡了(再次得到监视器锁).
固然, 上面还有另一种状况, 假如你不知道快递员何时会来, 可能在一小时后, 也多是明天, 那总不能一直干等着不洗澡吧, 因而你决定, 我就等一个小时(调用带超时时间的wait(long timeout)
方法), 一小时后快递还不来, 就不等了, 大不了用沐浴露凑合着洗洗头 o(TヘTo)
上面只是拿生活中的例子打了个比方, 不知道你们理解了没有, 下面咱们就来正经的看看代码.
以上5个都方法定义在了java的Object类中, 这意味着java中全部的类都会继承这些方法.
同时, 下面的源码分析中咱们将看到, 这些方法都是final
类型的, 也就是说全部的子类都不能改写这些方法.
下面咱们来看源码:
(这一段会比较长, 不想看源码分析的能够直接跳过这一部分看结论)
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
方法,这里插一句, 关于第四条, 解释了无参的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方法都是native方法:
public final native void notify(); public final native void notifyAll();
相比于wait方法, 这两个方法的源码注释要少一点, 咱们就不分段看了, 直接看所有的
/** * 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
方法时, 线程还在同步代码块里面, 只有离开了同步代码块, 锁才会被释放)这个方法应当只被持有监视器锁的线程调用, 一个线程能够经过如下三种方法之一得到this object
的监视器锁:
synchonized
代码块, 该代码块以this object
做为锁咱们经过上一篇介绍synchronized同步代码块的文章知道, synchronized做用于类的静态方法时, 是拿类的Class对象做为锁, 做用于类的普通方法或者 synchronized(this){}
代码块时, 是拿当前类的实例对象做为监视器锁, 这里的this object
, 指的应该是该线程调用notify方法所持有的锁对象.
/** * 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
方法,wait set
中移除, 从新参与到线程调度中, 而且和其余线程同样, 竞争锁资源, 一旦它又得到了监视器锁, 则它在调用wait方法时的全部状态都会被恢复, 这里要注意“假唤醒”的问题.wait set
以前或者在wait set
之中时, 若是被其余线程中断了, 则会抛出InterruptedException
异常, 可是, 若是是在恢复现场的过程当中被中断了, 则直到现场恢复完成后才会抛出InterruptedException
this object
的wait set
里, 也只会释放当前监视器锁(this object
), 若是当前线程还持有了其余同步资源, 则即便它在this object
中的等待队列中, 也不会释放.本篇中屡次提到了monitor
, this object
, wait set
等概念,这些都表明什么意思?
监视器锁究竟是怎么获取和释放的?
咱们将在下一篇文章讨论这个问题。
(完)
查看更多系列文章:系列文章目录