Object的wait、notify和notifyAll

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 }

运行结果:

开始阻塞啦
阻塞结束啦

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

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

* 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.

翻译过来:

当前线程必须拥有此对象监视器。该线程释放对此监视器的所有权并等待,直到其他线程通过调用 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行可以看到monitorentermonitorexit两条指令,monitorenter是尝试获取monitor,如果成功则执行,不成功则阻塞等待。monitorexit是释放monitor

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

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

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

翻译大概是:

通过执行此对象的同步实例方法。 
通过执行在此对象上进行同步的 synchronized 语句的代码块。 
对于 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 }

执行结果:

开始阻塞啦
Exception in thread "main" java.lang.IllegalMonitorStateException
    at java.lang.Object.notify(Native Method)
    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 }

 

执行结果:

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

原因:

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

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

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值