Obect的wait、notify 和 notifyAll是Object提供的同步方法,也就是所有对象都生而带来的方法,估计搞java的没有不知道这几个方法的。那么他究竟是怎么使用的呢?在此处记录一下自己的理解。

先上一个最最最简单的例子。

  1. public class SynchronizedTest {
  2. public static void main(String[] args) throws Exception {
  3. Thread mt = new Thread(){
  4. @Override
  5. public void run() {
  6. synchronized (this) {
  7. System.out.println("开始阻塞啦");
  8. try {
  9. this.wait();
  10. } catch (InterruptedException e) {
  11. e.printStackTrace();
  12. }
  13. System.out.println("阻塞结束啦");
  14. }
  15. }
  16. };
  17. mt.start();
  18. Thread.sleep(500);
  19. synchronized (mt) {
  20. mt.notify();
  21. }
  22. }
  23. }

运行结果:

  1. 开始阻塞啦
  2. 阻塞结束啦

上面的例子中,wait和notify方法都是在synchronized代码体中执行的,如果没有经过synchronized修饰,直接使用则会抛出java.lang.IllegalMonitorStateException异常。

至于原因,jdk源码wait方法中的描述为:

  1. * The current thread must own this object's monitor. The thread
  2. * releases ownership of this monitor and waits until another thread
  3. * notifies threads waiting on this object's monitor to wake up
  4. * either through a call to the {@code notify} method or the
  5. * {@code notifyAll} method. The thread then waits until it can
  6. * re-obtain ownership of the monitor and resumes execution.

翻译过来:

  1. 当前线程必须拥有此对象监视器。该线程释放对此监视器的所有权并等待,直到其他线程通过调用 notify 方法,或 notifyAll 方法通知在此对象的监视器上等待的线程醒来。然后该线程将等到重新获得对监视器的所有权后才能继续执行。

总结过来就是:要想使用wait等一系列方法,必须拥有当前对象的监视器(很多地方称为监视器锁)

那么什么是对象的监视器呢?

简单说监视器是java对象为实现同步操作的一种机制,使用javap查看上边例子的线程部分的反编译指令:

  1. final class com.xxx.SynchronizedTest$1 extends java.lang.Thread {
  2. com.xxx.SynchronizedTest$1();
  3. Code:
  4. 0: aload_0
  5. 1: invokespecial #1 // Method java/lang/Thread."<init>":()V
  6. 4: return
  7.  
  8. public void run();
  9. Code:
  10. 0: aload_0
  11. 1: dup
  12. 2: astore_1
  13. 3: monitorenter
  14. 4: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
  15. 7: ldc #3 // String 开始阻塞啦
  16. 9: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
  17. 12: aload_0
  18. 13: invokevirtual #5 // Method java/lang/Object.wait:()V
  19. 16: goto 24
  20. 19: astore_2
  21. 20: aload_2
  22. 21: invokevirtual #7 // Method java/lang/InterruptedException.printStackTrace:()V
  23. 24: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
  24. 27: ldc #8 // String 阻塞结束啦
  25. 29: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
  26. 32: aload_1
  27. 33: monitorexit
  28. 34: goto 42
  29. 37: astore_3
  30. 38: aload_1
  31. 39: monitorexit
  32. 40: aload_3
  33. 41: athrow
  34. 42: return
  35. Exception table:
  36. from to target type
  37. 12 16 19 Class java/lang/InterruptedException
  38. 4 34 37 any
  39. 37 40 37 any
  40. }

13行和27行可以看到monitorenter和monitorexit两条指令,monitorenter是尝试获取monitor,如果成功则执行,不成功则阻塞等待。monitorexit是释放monitor

那么如何拥有该对象的监视器呢?

jdk源码notify方法中列举了三种方法:

  1. By executing a synchronized instance method of that object.
  2. By executing the body of a {@code synchronized} statement
  3.   that synchronizes on the object.
  4. For objects of type {@code Class,} by executing a
  5.   synchronized static method of that class.

翻译大概是:

  1. 通过执行此对象的同步实例方法。
  2. 通过执行在此对象上进行同步的 synchronized 语句的代码块。
  3. 对于 Class 类型的对象,可以通过执行该类的同步静态方法。

我理解的就是被synchronized 修饰的方法或代码块(如果是代码块,需要使用同一对象做为synchronized的参数,目的是为了获取相同的监视器)。结合上边反编译的线程匿名内部类指令可以看到,使用synchronized 修饰的代码会使用monitorenter指令获取监视器,wait和notify必须获得监视器才能正确执行。

下面列举一下针对文章开始实例的错误示范:

错误实例1

  1. public class SynchronizedTest {
  2. public static void main(String[] args) throws Exception {
  3. Thread mt = new Thread(){
  4. @Override
  5. public void run() {
  6. synchronized (this) {
  7. System.out.println("开始阻塞啦");
  8. try {
  9. this.wait();
  10. } catch (InterruptedException e) {
  11. e.printStackTrace();
  12. }
  13. System.out.println("阻塞结束啦");
  14. }
  15. }
  16. };
  17. mt.start();
  18. Thread.sleep(500);
  19. // synchronized (mt) {
  20. mt.notify();
  21. // }
  22. }
  23. }

执行结果:

  1. 开始阻塞啦
  2. Exception in thread "main" java.lang.IllegalMonitorStateException
  3. at java.lang.Object.notify(Native Method)
  4. at xxx

原因:

  notify方法没有获得监视器。

错误实例2:

  1. public class SynchronizedTest {
  2. public static void main(String[] args) throws Exception {
  3. Thread mt = new Thread(new Runnable() {
  4. @Override
  5. public void run() {
  6. synchronized (this) {
  7. System.out.println("开始阻塞啦");
  8. try {
  9. this.wait();
  10. } catch (InterruptedException e) {
  11. e.printStackTrace();
  12. }
  13. System.out.println("阻塞结束啦");
  14. }
  15. }
  16. });
  17. mt.start();
  18. Thread.sleep(500);
  19. synchronized (mt) {
  20. mt.notify();
  21. }
  22. }
  23. }

执行结果:

  1. 开始阻塞啦
    (线程持续wait中。。。

原因:

  这个例子是我最开始的写法,放在这里有点不太合适,因为他并不是wait和notify错误使用导致的问题,而是错误使用Runnable导致的,最终我还是决定放上来吧,防止有人也会一时想不明白。

  例子中使用 new Runnable 创建了一个匿名内部类并作为构造参数传给new Thread,导致构造的对象mt和匿名内部类的this不是同一个对象。所以导致notify不起作用= =、

Object的wait、notify和notifyAll的更多相关文章

  1. 使用Object的wait,notify,notifyAll做线程调度

    我们知道java中的所有类的祖先都是Object,Object类有四个个方法wait(),wait(long timeout),notify(),notifyAll(),这四个方法可以用来做线程的调度 ...

  2. java锁与监视器概念 为什么wait、notify、notifyAll定义在Object中 多线程中篇(九)

    在Java中,与线程通信相关的几个方法,是定义在Object中的,大家都知道Object是Java中所有类的超类 在Java中,所有的类都是Object,借助于一个统一的形式Object,显然在有些处 ...

  3. object的wait()、notify()、notifyAll()、方法和Condition的await()、signal()方法

    wait().notify()和notifyAll()是 Object类 中的方法 从这三个方法的文字描述可以知道以下几点信息: 1)wait().notify()和notifyAll()方法是本地方 ...

  4. Object的wait/notify/notifyAll&&Thread的sleep/yield/join/holdsLock

    一.wait/notify/notifyAll都是Object类的实例方法 1.wait方法:阻塞当前线程等待notify/notifyAll方法的唤醒,或等待超时后自动唤醒. wait等待其实是对象 ...

  5. Java-JUC(九):使用Lock替换synchronized,使用Condition的await,singal,singalall替换object的wait,notify,notifyall实现线程间的通信

    Condition: condition接口描述了可能会与锁有关的条件变量.这些用法上与使用object.wait访问隐式监视器类似,但提供了更强大的功能.需要特别指出的是,单个lock可能与多个Co ...

  6. Thread之七:Object里的wait、notify、notifyAll的使用方法

    wait().notify().notifyAll()是三个定义在Object类里的方法,可以用来控制线程的状态 public final native void notify(); public f ...

  7. 如何在 Java 中正确使用 wait, notify 和 notifyAll(转)

    wait, notify 和 notifyAll,这些在多线程中被经常用到的保留关键字,在实际开发的时候很多时候却并没有被大家重视.本文对这些关键字的使用进行了描述. 在 Java 中可以用 wait ...

  8. 线程同步以及 yield() wait()和notify()、notifyAll()

    1.yield() 该方法与sleep()类似,只是不能由用户指定暂停多长时间,并且yield()方法只能让同优先级的线程有执行的机会. 2.wait()和notify().notifyAll() 这 ...

  9. Java Thread wait, notify and notifyAll Example

    Java Thread wait, notify and notifyAll Example Java线程中的使用的wait,notify和nitifyAll方法示例. The Object clas ...

随机推荐

  1. odoo添加顶部按钮实现自定义方法

    一.效果图 自定义添加顶部按钮,实现自定义方法. 二.实现过程 1.需要用到三个文件,tree_view_button.js.tree_view_odoo.xml.base.xml三个文件,文件目录如 ...

  2. 剑指Offer(二十一):栈的压入、弹出序列

    剑指Offer(二十一):栈的压入.弹出序列 搜索微信公众号:'AI-ming3526'或者'计算机视觉这件小事' 获取更多算法.机器学习干货 csdn:https://blog.csdn.net/b ...

  3. MySQL数据库之单表查询中关键字的执行顺序

    目录 MySQL数据库之单表查询中关键字的执行顺序 1 语法顺序 2 执行顺序 3 关键字使用语法 MySQL数据库之单表查询中关键字的执行顺序 1 语法顺序 select distinct from ...

  4. Azure DevOps vsts-agent-linux 安装出错, Must not run with sudo

    在linux安装 vsts-agent-linux 在vsts-agent-linux的解压目录运行./config.sh, 提示"Must not run with sudo", ...

  5. HDU 6313

    题意略. 思路:数论题. #include<bits/stdc++.h> using namespace std; ; const int maxn = p * p; ][maxn + ] ...

  6. Oracle数据库测试和优化最佳实践: OTest介绍 (转)

    当前Oracle数据库最佳测试工具OTest *  Otest是用于Oracle数据库测试.优化.监控软件. *  Otest是免费提供给Oracle客户和广大DBA工程师使用的软件.由原厂技术专家王 ...

  7. centos7安装使用docker-tomcat-mysql

    windows安装centos虚拟机 下载安装 virtualBox下载 https://mirrors.tuna.tsinghua.edu.cn/help/virtualbox/ centos7镜像 ...

  8. hdu6437 Problem L.Videos(网络流)

    Problem L.Videos Problem Description: C-bacteria takes charge of two kinds of videos: ’The Collectio ...

  9. HDU 5451 Best Solver 数论 快速幂 2015沈阳icpc

    Best Solver Time Limit: 1500/1000 MS (Java/Others)    Memory Limit: 65535/102400 K (Java/Others)Tota ...

  10. CodeM 美团资格赛 思维 dfs

    链接:https://www.nowcoder.com/acm/contest/138/C来源:牛客网 世界杯就要开始啦!真真正正的战斗从淘汰赛开始,现在我们给出球队之间的胜负概率,来预测每支球队夺冠 ...