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解决:

复制代码
复制代码
 1 public class OutputThread implements Runnable {
 2  3 private int num;  4 private Object lock;  5  6 public OutputThread(int num, Object lock) {  7 super();  8 this.num = num;  9 this.lock = lock; 10  } 11 12 public void run() { 13 try { 14 while(true){ 15 synchronized(lock){ 16  lock.notifyAll(); 17  lock.wait(); 18  System.out.println(num); 19  } 20  } 21 } catch (InterruptedException e) { 22 // TODO Auto-generated catch block 23  e.printStackTrace(); 24  } 25 26  } 27 28 public static void main(String[] args){ 29 final Object lock = new Object(); 30 31 Thread thread1 = new Thread(new OutputThread(1,lock)); 32 Thread thread2 = new Thread(new OutputThread(2, lock)); 33 34  thread1.start(); 35  thread2.start(); 36  } 37 38 }
复制代码
复制代码

 

《Java Concurrency in Practice》里的第14章,对wait, notify有更加详细的介绍。



本文转自农夫山泉别墅博客园博客,原文链接:http://www.cnblogs.com/yaowen/p/6134562.html,如需转载请自行联系原作者

相关文章
|
2月前
|
安全 Java
Java中WAIT和NOTIFY方法调用时机的深层解析
在Java多线程编程中,`wait()`和`notify()`方法的正确使用对于线程间的协调至关重要。这两个方法必须在同步块或同步方法中调用,这一规定的深层原因是什么呢?本文将深入探讨这一机制。
51 5
|
2月前
|
安全 Java 开发者
Java中WAIT和NOTIFY方法必须在同步块中调用的原因
在Java多线程编程中,`wait()`和`notify()`方法是实现线程间协作的关键。这两个方法必须在同步块或同步方法中调用,这一要求背后有着深刻的原因。本文将深入探讨为什么`wait()`和`notify()`方法必须在同步块中调用,以及这一机制如何确保线程安全和避免死锁。
54 4
|
2月前
|
安全 Java 开发者
深入解读JAVA多线程:wait()、notify()、notifyAll()的奥秘
在Java多线程编程中,`wait()`、`notify()`和`notifyAll()`方法是实现线程间通信和同步的关键机制。这些方法定义在`java.lang.Object`类中,每个Java对象都可以作为线程间通信的媒介。本文将详细解析这三个方法的使用方法和最佳实践,帮助开发者更高效地进行多线程编程。 示例代码展示了如何在同步方法中使用这些方法,确保线程安全和高效的通信。
81 9
|
2月前
|
Java
JAVA多线程通信:为何wait()与notify()如此重要?
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是实现线程间通信的核心机制。它们通过基于锁的方式,使线程在条件不满足时进入休眠状态,并在条件满足时被唤醒,从而确保数据一致性和同步。相比其他通信方式,如忙等待,这些方法更高效灵活。 示例代码展示了如何在生产者-消费者模型中使用这些方法实现线程间的协调和同步。
46 3
|
算法 Java Linux
每日一面 - java里的wait()和sleep()的区别有哪些?
每日一面 - java里的wait()和sleep()的区别有哪些?
每日一面 - java里的wait()和sleep()的区别有哪些?
|
Java 调度
java中wait和sleep的区别
java中wait和sleep的区别
|
7天前
|
监控 Java
java异步判断线程池所有任务是否执行完
通过上述步骤,您可以在Java中实现异步判断线程池所有任务是否执行完毕。这种方法使用了 `CompletionService`来监控任务的完成情况,并通过一个独立线程异步检查所有任务的执行状态。这种设计不仅简洁高效,还能确保在大量任务处理时程序的稳定性和可维护性。希望本文能为您的开发工作提供实用的指导和帮助。
46 17
|
18天前
|
Java
Java—多线程实现生产消费者
本文介绍了多线程实现生产消费者模式的三个版本。Version1包含四个类:`Producer`(生产者)、`Consumer`(消费者)、`Resource`(公共资源)和`TestMain`(测试类)。通过`synchronized`和`wait/notify`机制控制线程同步,但存在多个生产者或消费者时可能出现多次生产和消费的问题。 Version2将`if`改为`while`,解决了多次生产和消费的问题,但仍可能因`notify()`随机唤醒线程而导致死锁。因此,引入了`notifyAll()`来唤醒所有等待线程,但这会带来性能问题。
Java—多线程实现生产消费者
|
3天前
|
缓存 安全 算法
Java 多线程 面试题
Java 多线程 相关基础面试题
|
20天前
|
安全 Java Kotlin
Java多线程——synchronized、volatile 保障可见性
Java多线程中,`synchronized` 和 `volatile` 关键字用于保障可见性。`synchronized` 保证原子性、可见性和有序性,通过锁机制确保线程安全;`volatile` 仅保证可见性和有序性,不保证原子性。代码示例展示了如何使用 `synchronized` 和 `volatile` 解决主线程无法感知子线程修改共享变量的问题。总结:`volatile` 确保不同线程对共享变量操作的可见性,使一个线程修改后,其他线程能立即看到最新值。