JAVA多线程中wait()方法的详细分析

转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/119645679
本文出自【赵彦军的博客】

最近看帖子,发现一道面试题:

启动两个线程, 一个输出 1,3,5,7…99, 另一个输出 2,4,6,8…100 最后 STDOUT 中按序输出 1,2,3,4,5…100

题目要求用 Java 的 wait + notify 机制来实现,重点考察对于多线程可见性的理解。

wait 和 notify 简介

wait 和 notify 均为 Object 的方法:

  • Object.wait() —— 暂停一个线程
  • Object.notify() —— 唤醒一个线程

从以上的定义中,我们可以了解到以下事实:

  • 想要使用这两个方法,我们需要先有一个对象 Object。
  • 在多个线程之间,我们可以通过调用同一个对象的wait()和notify()来实现不同的线程间的可见。

对象控制权(monitor)

在使用 wait 和 notify 之前,我们需要先了解对象的控制权(monitor)。在 Java 中任何一个时刻,对象的控制权只能被一个线程拥有。如何理解控制权呢?请先看下面的简单代码:

public class Util {

    public void run(){
        Object ob = new Object();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                        ob.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

直接执行,我们将会得到以下异常:

E/AndroidRuntime: FATAL EXCEPTION: Thread-3
    Process: com.example.myapplication, PID: 12211
    java.lang.IllegalMonitorStateException: object not locked by thread before wait()
        at java.lang.Object.wait(Native Method)
        at java.lang.Object.wait(Object.java:442)
        at java.lang.Object.wait(Object.java:568)
        at com.example.myapplication.Util$1.run(Util.java:20)
        at java.lang.Thread.run(Thread.java:929)

出错的代码在:object.wait();。这里我们需要了解以下事实:

  • 无论是执行对象的 wait、notify 还是 notifyAll 方法,必须保证当前运行的线程取得了该对象的控制权(monitor)
  • 如果在没有控制权的线程里执行对象的以上三种方法,就会报 java.lang.IllegalMonitorStateException 异常。
  • JVM 基于多线程,默认情况下不能保证运行时线程的时序性

在上面的示例代码中,我们 new 了一个 Thread,但是对象 object 的控制权仍在主线程里。所以会报 java.lang.IllegalMonitorStateException 。

我们可以通过同步锁来获得对象控制权,例如:synchronized 代码块。对以上的示例代码做改造:

public class Util {

    public void run(){
        Object ob = new Object();
        
        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (ob){
                    try {
                        ob.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
        
    }
}

再次执行,代码不再报错。

我们可以得到以下结论:

  • 调用对象的wait()和notify()方法,需要先取得对象的控制权
  • 可以使用synchronized (object)来取得对于 object 对象的控制权

解题

了解了对象控制权之后,我们就可以正常地使用 notify 和 wait 了,下面给出我的解题方法,供参考。

方法一:

public class Util {

    public void run(){
        Object ob = new Object();

        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (ob){
                    try {
                        for (int i = 0 ;i < 100; i=i+2){
                            ob.notify();
                            Log.d("num--", "" + i); //偶数
                            ob.wait();
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (ob){
                    try {
                        for (int i = 1 ;i < 100; i=i+2){
                            ob.notify();
                            Log.d("num------", "" + i);  //奇数
                            ob.wait();
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }
}

方法二:

public class Util {

    public void run(){
        Object ob = new Object();

        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (ob){
                    try {
                        for (int i = 0 ;i < 100; i=i+2){
                            Log.d("num--", "" + i); //偶数
                            ob.notifyAll();
                            ob.wait();
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (ob){
                    try {
                        for (int i = 1 ;i < 100; i=i+2){
                            Log.d("num------", "" + i);  //奇数
                            ob.notifyAll();
                            ob.wait();
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }
}

发散:notify()和notifyAll()

这两个方法均为 native 方法,在JDK 1.8 中的关于notify()的JavaDoc如下:

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.

译为:

唤醒此 object 控制权下的一个处于 wait 状态的线程。若有多个线程处于此 object 控制权下的 wait 状态,只有一个会被唤醒。

也就是说,如果有多个线程在 wait 状态,我们并不知道哪个线程会被唤醒。

在JDK 1.8 中的关于notifyAll()的JavaDoc如下:

Wakes up all threads that are waiting on this object’s monitor.

译为:

唤醒所有处于此 object 控制权下的 wait 状态的线程。

所以,我们需要根据实际的业务场景来考虑如何使用。

扩展

关于对象锁,notify只是唤醒一个线程B,B这个线程要等到当前对象释放synchronized的对象锁才能执行,也就是A flag.wait()才能执行。再用高级点的说法,就是notify是等待池进入锁池。

wait()方法 表示持有对象锁的线程A准备释放对象锁权限,释放CPU资源并进入等待。

Wait() 和notify() 方法只能从synchronized方法或块中调用,需要在其他线程正在等待的对象上调用notify方法。

notifyAll 通知JVM唤醒所有竞争该对象锁的线程,线程A synchronized 代码作用域结束后,JVM通过算法将对象锁权限指派给某个线程X,所有被唤醒的线程不再等待。线程X synchronized 代码作用域结束后,之前所有被唤醒的线程都有可能获得该对象锁权限,这个由JVM算法决定。

通俗比喻:

有两个人A和B都要和一个女孩G约会(A线程和B线程都调用synchronized约会方法,锁对象G.class)。

A率先和G约会了(A线程进入synchronized方法),B只能等着。

A在约会途中被电话叫走,把G凉在那里(调用G.class.wait())。

这时候B发现G没人约会了,于是上场(B线程进入synchronized方法)。

等到B和G约会完成,B又打电话叫A回来继续约会(B线程调用G.class.notify()方法),A才回来继续约会直到完成。

notifyAll()就是不止A和B了,可能有更多的人要和G约会,等A走了之后某个人上场。

  • 16
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值