前言

上一篇文章我们讲了java的同步代码块, 这一篇我们来看看同步代码块之间的协作与通信.
阅读本篇前你需要知道什么是同步代码块, 什么是监视器锁, 还不是很了解的同学建议先去看一看上一篇文章.

概述

在Java中, 我们可以使用

  • wait()
  • wait(long timeout)
  • wait(long timeout, int nanos)
  • notify()
  • notifyAll()

这5个方法来实现同步代码块之间的通信, 注意, 我说的是同步代码块之间的通信, 这意味着: 调用该方法的当前线程必须持有对象的监视器锁(源码注释: The current thread must own this object’s monitor.) 其实, 这句话换个通俗点的说法就是: 只能在同步代码块中使用这些方法.
**
道理很简单, 因为只有进入了同步代块, 才能获得监视器锁.

wait方法的作用是, 阻塞当前线程(阻塞的原因常常是一些必要的条件还没有满足), 让出监视器锁, 不再参与锁竞争, 直到其他线程来通知(告知必要的条件已经满足了), 或者直到设定的超时等待时间到了.

notifynotifyAll方法的作用是, 通知那些调用了wait方法的线程, 让它们从wait处返回.
可见, waitnotify 方法一般是成对使用的, 我把它简单的总结为: 等通知> <br /> wait 是等, notify 是通知.
为了给大家一个感性的认识, 我这里打个比方:

假设你和舍友一起租了个两室一厅一厨一卫的房子, 天这么热, 当然每天都要洗澡啦, 但是卫生间只有一个, 同一时间, 只有一个人能用.

这时候, 你先下班回来了, 准备要洗澡, 刚进浴室, 突然想起来你的专用防脱洗发膏用完了, 查了下快递说是1小时后才能送到, 但这时候你的舍友回来了, 他也要洗澡, 所以你总不能”站着茅坑不拉屎”吧, 所以你主动让出了浴室(调用wait方法, 让出监视器锁), 让舍友先洗, 自己快递.

过了一个小时, 快递送来了你的防脱洗发膏(调用了nofity方法, 唤醒在wait中的线程), 你现在需要洗澡的资源都有了, 万事俱备, 就差进入浴室了, 这个时候你去浴室门口一看, 嘿, 浴室空着!(当前没有线程占用监视器锁) 舍友已经洗好了! 于是你高高兴兴的带着你的防脱洗发水进去洗澡了(再次获得监视器锁).

当然, 上面还有另外一种情况, 假如你不知道快递员什么时候会来, 可能在一小时后, 也可能是明天, 那总不能一直干等着不洗澡吧, 于是你决定, 我就等一个小时(调用带超时时间的wait(long timeout)方法), 一小时后快递还不来, 就不等了, 大不了用沐浴露凑合着洗洗头 o(TヘTo)

源码分析

以上5个都方法定义在了java的Object类中, 这意味着java中所有的类都会继承这些方法.
同时, 下面的源码分析中我们将看到, 这些方法都是final类型的, 也就是说所有的子类都不能改写这些方法.

下面我们来看源码:

wait方法

  1. public final void wait() throws InterruptedException {
  2. wait(0);
  3. }
  4. public final void wait(long timeout, int nanos) throws InterruptedException {
  5. if (timeout < 0) {
  6. throw new IllegalArgumentException("timeout value is negative");
  7. }
  8. if (nanos < 0 || nanos > 999999) {
  9. throw new IllegalArgumentException("nanosecond timeout value out of range");
  10. }
  11. if (nanos > 0) {
  12. timeout++;
  13. }
  14. wait(timeout);
  15. }
  16. 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方法”看起来应该是什么样子的”, 我们一段一段来看:
(这里我把原文也贴出来了, 是怕自己翻译的不够精确, 英语好的可以直接看原文)