Java的wait(), notify()和notifyAll()使用心得

 更新时间:2013年08月21日 14:46:00   作者:  
本篇文章是对java的 wait(),notify(),notifyAll()进行了详细的分析介绍,需要的朋友参考下
wait(),notify()和notifyAll()都是java.lang.Object的方法:
wait(): Causes the current thread to wait until another thread invokes the notify() method or the notifyAll() method for this object.
notify(): Wakes up a single thread that is waiting on this object's monitor.
notifyAll(): Wakes up all threads that are waiting on this object's monitor.
这三个方法,都是Java语言提供的实现线程间阻塞(Blocking)和控制进程内调度(inter-process communication)的底层机制。在解释如何使用前,先说明一下两点:
1. 正如Java内任何对象都能成为锁(Lock)一样,任何对象也都能成为条件队列(Condition queue)。而这个对象里的wait(), notify()和notifyAll()则是这个条件队列的固有(intrinsic)的方法。
2. 一个对象的固有锁和它的固有条件队列是相关的,为了调用对象X内条件队列的方法,你必须获得对象X的锁。这是因为等待状态条件的机制和保证状态连续性的机制是紧密的结合在一起的。
(An object's intrinsic lock and its intrinsic condition queue are related: in order to call any of the condition queue methods on object X, you must hold the lock on X. This is because the mechanism for waiting for state-based conditions is necessarily tightly bound to the mechanism fo preserving state consistency)
根据上述两点,在调用wait(), notify()或notifyAll()的时候,必须先获得锁,且状态变量须由该锁保护,而固有锁对象与固有条件队列对象又是同一个对象。也就是说,要在某个对象上执行wait,notify,先必须锁定该对象,而对应的状态变量也是由该对象锁保护的。
知道怎么使用后,我们来问下面的问题:
1. 执行wait, notify时,不获得锁会如何?
请看代码:
复制代码 代码如下:

public static void main(String[] args) throws InterruptedException {
        Object obj = new Object();
        obj.wait();
        obj.notifyAll();
}

执行以上代码,会抛出java.lang.IllegalMonitorStateException的异常。
2. 执行wait, notify时,不获得该对象的锁会如何?
请看代码:
复制代码 代码如下:

public static void main(String[] args) throws InterruptedException {
        Object obj = new Object();
        Object lock = new Object();
        synchronized (lock) {
            obj.wait();
            obj.notifyAll();
        }
    }

执行代码,同样会抛出java.lang.IllegalMonitorStateException的异常。
3. 为什么在执行wait, notify时,必须获得该对象的锁?
这是因为,如果没有锁,wait和notify有可能会产生竞态条件(Race Condition)。考虑以下生产者和消费者的情景:
1.1生产者检查条件(如缓存满了)-> 1.2生产者必须等待
2.1消费者消费了一个单位的缓存 -> 2.2重新设置了条件(如缓存没满) -> 2.3调用notifyAll()唤醒生产者
我们希望的顺序是: 1.1->1.2->2.1->2.2->2.3
但在多线程情况下,顺序有可能是 1.1->2.1->2.2->2.3->1.2。也就是说,在生产者还没wait之前,消费者就已经notifyAll了,这样的话,生产者会一直等下去。
所以,要解决这个问题,必须在wait和notifyAll的时候,获得该对象的锁,以保证同步。
请看以下利用wait,notify实现的一个生产者、一个消费者和一个单位的缓存的简单模型:
复制代码 代码如下:

public class QueueBuffer {
    int n;
    boolean valueSet = false;
    synchronized int get() {
        if (!valueSet)
            try {
                wait();
            } catch (InterruptedException e) {
                System.out.println("InterruptedException caught");
            }
        System.out.println("Got: " + n);
        valueSet = false;
        notify();
        return n;
    }
    synchronized void put(int n) {
        if (valueSet)
            try {
                wait();
            } catch (InterruptedException e) {
                System.out.println("InterruptedException caught");
            }
        this.n = n;
        valueSet = true;
        System.out.println("Put: " + n);
        notify();
    }
}

复制代码 代码如下:

public class Producer implements Runnable {
    private QueueBuffer q;
    Producer(QueueBuffer q) {
        this.q = q;
        new Thread(this, "Producer").start();
    }
    public void run() {
        int i = 0;
        while (true) {
            q.put(i++);
        }
    }
}

复制代码 代码如下:

public class Consumer implements Runnable {
    private QueueBuffer q;
    Consumer(QueueBuffer q) {
        this.q = q;
        new Thread(this, "Consumer").start();
    }
    public void run() {
        while (true) {
            q.get();
        }
    }
}

复制代码 代码如下:

public class Main {
    public static void main(String[] args) {
        QueueBuffer q = new QueueBuffer();
        new Producer(q);
        new Consumer(q);
        System.out.println("Press Control-C to stop.");
    }
}

所以,JVM通过在执行的时候抛出IllegalMonitorStateException的异常,来确保wait, notify时,获得了对象的锁,从而消除隐藏的Race Condition。
最后来看看一道题:写一个多线程程序,交替输出1,2,1,2,1,2......
利用wait, notify解决:
复制代码 代码如下:

public class OutputThread implements Runnable {
    private int num;
    private Object lock;
    public OutputThread(int num, Object lock) {
        super();
        this.num = num;
        this.lock = lock;
    }
    public void run() {
        try {
            while(true){
                synchronized(lock){
                    lock.notifyAll();
                    lock.wait();
                    System.out.println(num);
                }
            }
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    public static void main(String[] args){
        final Object lock = new Object();
        Thread thread1 = new Thread(new OutputThread(1,lock));
        Thread thread2 = new Thread(new OutputThread(2, lock));
        thread1.start();
        thread2.start();
    }
}

相关文章

  • 分析JVM的组成结构

    分析JVM的组成结构

    JVM(虚拟机):指以软件的方式模拟具有完整硬件系统功能、运行在一个完全隔离环境中的完整计算机系统 ,是物理机的软件实现。JVM和VMware,Virtual Box等虚拟机一样,都是运行在操作系统之上的计算机系统
    2021-06-06
  • 浅谈单例模式和线程安全问题

    浅谈单例模式和线程安全问题

    这篇文章主要介绍了浅谈单例模式和线程安全问题,再某些特殊的情况下,存在一个类仅能用来产生一个唯一对象的必要性,因此需要单例模式,需要的朋友可以参考下
    2023-04-04
  • Java JWT实现跨域身份验证方法详解

    Java JWT实现跨域身份验证方法详解

    JWT(JSON Web Token)是目前流行的跨域认证解决方案,是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。本文将介绍JWT如何实现跨域身份验证,感兴趣的可以学习一下
    2022-01-01
  • springboot如何获取applicationContext servletContext

    springboot如何获取applicationContext servletContext

    这篇文章主要介绍了springboot如何获取applicationContext servletContext问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-01-01
  • Java字符串常量池和intern方法解析

    Java字符串常量池和intern方法解析

    本文主要介绍了Java字符串常量池和intern方法解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-05-05
  • 基于ArrayList初始化长度的作用及影响

    基于ArrayList初始化长度的作用及影响

    这篇文章主要介绍了基于ArrayList初始化长度的作用及影响,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-03-03
  • Java计算黑洞数的方法示例

    Java计算黑洞数的方法示例

    这篇文章主要介绍了Java计算黑洞数的方法,简单描述了黑洞数的概念及具体计算方法,涉及java数值运算相关操作技巧,需要的朋友可以参考下
    2017-12-12
  • java类访问权限与成员访问权限解析

    java类访问权限与成员访问权限解析

    这篇文章主要针对java类访问权限与成员访问权限进行解析,对类与成员访问权限进行验证,感兴趣的小伙伴们可以参考一下
    2016-02-02
  • java8 Instant 时间及转换操作

    java8 Instant 时间及转换操作

    这篇文章主要介绍了java8 Instant 时间及转换操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-09-09
  • java题解LeetCode454.四数相加示例

    java题解LeetCode454.四数相加示例

    这篇文章主要为大家介绍了java题解LeetCode454.四数相加示例思路解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-10-10

最新评论