java 为什么wait(),notify(),notifyAll()必须在同步方法/代码块中调用?

Java同步机制详解
本文深入解析了Java中监视器的概念,解释了为什么wait(), notify() 和 notifyAll() 方法必须在同步方法或同步代码块中调用。通过实例阐述了这些方法的作用及调用时机的重要性。
在Java中,所有对象都能够被作为"监视器monitor"——指一个拥有一个独占锁,一个入口队列和一个等待队列的实体entity。所有对象的非同步方法都能够在任意时刻被任意线程调用,此时不需要考虑加锁的问题。而对于对象的同步方法来说,在任意时刻有且仅有一个拥有该对象独占锁的线程能够调用它们。例如,一个同步方法是独占的。如果在线程调用某一对象的同步方法时,对象的独占锁被其他线程拥有,那么当前线程将处于阻塞状态,并添加到对象的入口队列中。
只有在调用线程拥有某个对象的独占锁时,才能够调用该对象的wait(),notify()和notifyAll()方法。这一点通常不会被程序员注意,因为程序验证通常是在对象的同步方法或同步代码块中调用它们的。如果尝试在未获取对象锁时调用这三个方法,那么你将得到一个"java.lang.IllegalMonitorStateException:current thread not owner"。
当一个线程正在某一个对象的同步方法中运行时调用了这个对象的wait()方法,那么这个线程将释放该对象的独占锁并被放入这个对象的等待队列。注意,wait()方法强制当前线程释放对象锁。这意味着在调用某对象的wait()方法之前,当前线程必须已经获得该对象的锁。因此,线程必须在某个对象的同步方法或同步代码块中才能调用该对象的wait()方法。
当某线程调用某对象的notify()或notifyAll()方法时,任意一个(对于notify())或者所有(对于notifyAll())在该对象的等待队列中的线程,将被转移到该对象的入口队列。接着这些队列(译者注:可能只有一个)将竞争该对象的锁,最终获得锁的线程继续执行。如果没有线程在该对象的等待队列中等待获得锁,那么notify()和notifyAll()将不起任何作用。在调用对象的notify()和notifyAll()方法之前,调用线程必须已经得到该对象的锁。因此,必须在某个对象的同步方法或同步代码块中才能调用该对象的notify()或notifyAll()方法。
对于处于某对象的等待队列中的线程,只有当其他线程调用此对象的notify()或notifyAll()方法时才有机会继续执行。
调用wait()方法的原因通常是,调用线程希望某个特殊的状态(或变量)被设置之后再继续执行。调用notify()或notifyAll()方法的原因通常是,调用线程希望告诉其他等待中的线程:"特殊状态已经被设置"。这个状态作为线程间通信的通道,它必须是一个可变的共享状态(或变量)。
例如,生产者线程向缓冲区中写入数据,消费者线程从缓冲区中读取数据。消费者线程需要等待直到生产者线程完成一次写入操作。生产者线程需要等待消费者线程完成一次读取操作。假设wait(),notify(),notifyAll()方法不需要加锁就能够被调用。此时消费者线程调用wait()正在进入状态变量的等待队列(译者注:可能还未进入)。在同一时刻,生产者线程调用notify()方法打算向消费者线程通知状态改变。那么此时消费者线程将错过这个通知并一直阻塞。因此,对象的wait(),notify(),notifyAll()方法必须在该对象的同步方法或同步代码块中被互斥地调用。
 
下面是原文:

Why wait(), notify(), notifyAll() must be called inside a synchronized method/block?

In Java, any object can act as a monitor - that's an entity with a single lock, an entry queue, and a waiting queue. An object's method without qualifed by the keywordsynchronized  can be invoked by any number of  threads at any time, the lock is ignored. The synchronized method of an object, one and only one thread, who owns the lock of that object, can be permitted to run that method at any time;i.e. asynchronized method is mutually exclusive . If, at the time of invocation, another thread owns the lock, then the calling thread will be put in the Blocked state and is added to the entry queue.

The wait()notify(), and notifyAll() methods should be called for an object only when the current thread has already locked the object's lock. This point sometimes goes unnoticed because programmers are used to calling these methods from withinsynchronized methods or blocks. Otherwise, you will get "java.lang.IllegalMonitorStateException: current thread not owner" at runtime.

When a thread running in a synchronized method of an object is calling the wait()method of the same object, that thread releases the lock of the object and is added to that object's waiting queue. As long as it's there, it sits idle. Note also that wait()forces the thread to release its lock. This means that it must own the lock of an object before calling the wait() method of that (same) object. Hence the thread must be in one of the object's synchronized methods or synchronized block before calling wait().

When a thread invokes an object's notify() or notifyAll() method, one (an arbitrary thread) or all of the threads in its waiting queue are removed from the waiting queueto the entry queue. They then actively contend for the object's lock, and the one that gets the lock goes on to execute. If no threads are waiting in the waiting queue, then notify() and notifyAll() have no effect. Before calling the notify() or notifyAll()method of an object, a thread must own the lock of the object. Hence it must be in one of the object's  synchronized methods or synchronized block.

A thread in the waiting queue of an object can run again only when some other thread calls the notify() (or the notifyAll) method of the same object.

The reason to call wait() is that the thread does not want to execute a block of code until a particular state to be achieved. It wants to wait until a particular state to be achieved. The reason to call notify() or notifyAll() method is that the thread will signal others that "a particular state has been achieved". The state is a communication channel between threads and it must be shared mutable state.

For example, one thread read data from a buffer and one thread write data into buffer. The reading data thread needs to wait until the writing data thread completly write a block data into the buffer. The wirting data thread needs to wait until the reading data thread completly read the data from the buffer. If wait()notify(), andnotifyAll() methods can be called by a ordinary method , the reading thread callswait() and the thread is being added to waiting queue . At just the same moment, the writing thread calls notify() to signal the condition changes. The reading thread misses the change and waits forever. Hence, they must be called inside a synchronized method or block which is mutually exclusive.

Reference

Chapter 20 of Inside the Java Virtual Machine - Thread Synchronization by Bill Venners

 

原文出处:http://www.xyzws.com/Javafaq/why-wait-notify-notifyall-must-be-called-inside-a-synchronized-method-block/127

转载自:http://baikkp.blog.51cto.com/3131132/1190175

<think>嗯,用户这次的问题聚焦在Javawait()方法必须在同步代码块调用的原因,这确实是多线程编程的核心问题。用户可能正在学习Java并发机制,或者遇到了IllegalMonitorStateException异常想深入理解原理。从ta引用多篇技术文章的行为看,应该是个严谨的学习者。 用户提供的引用资料很有价值,特别是[^3]明确指出wait()会强制释放锁,这需要前置的锁获取动作。而[^4]提到监视器模型,这是理解该机制的关键。需要把这些分散的知识点整合成逻辑链条。 核心矛盾在于:为什么看似简单的线程等待操作非要和synchronized绑定?这涉及三个层面: 1. 技术必要性:没有锁的情况下调用wait()会导致竞态条件(如检查条件后但等待前的状态突变) 2. 模型约束:Java对象监视器要求锁状态与等待队列严格绑定 3. 异常机制:设计者用IllegalMonitorStateException强制开发者遵守规则 特别要注意用户引用[^5]的代码示例,那个错误的take()方法演示了非同步调用wait()的危险——当buffer.isEmpty()判断后、执行wait()前,如果其他线程清空队列,将导致永久等待。这个例子必须重点分析。 在解释时需要强调"先检查后等待"这个操作必须是原子的,就像数据库事务的ACID特性。可以类比用户去银行:柜员(锁)确认账户没钱(条件检查)后让你去等候区(wait()),如果中途没锁门(不同步),其他人可能在你等待时存了钱却没人通知你。 最后生成的问题要覆盖延伸场景:notifyAll的使用选择、虚假唤醒的应对、Lock对象的差异,这些都是实际开发中必然遇到的关联问题。</think>Java中`wait()`方法必须在同步代码块同步方法调用的根本原因在于**确保线程协作的安全性和原子性**,其核心逻辑如下: --- ### 一、核心机制解析 1. **锁状态与条件检查的原子性** `wait()`的调用必须与**前置条件检查**(如`while(buffer.isEmpty())`)构成**原子操作**。若不在同步块中,可能发生以下危险时序: ``` 线程A:检查条件(如队列非空) → 准备调用wait() ↓ 线程B:修改状态(清空队列) → 调用notify() ↓ 线程A:执行wait() // 永久等待,因条件已失效且错过通知[^5] ``` 同步块通过锁保证:**条件检查 → 调用wait()** 过程不可分割,避免状态不一致。 2. **监视器锁(Monitor)的强制约束** - `wait()`会**主动释放对象锁**并进入等待状态[^3]。 - 释放锁的前提是**线程已持有该对象的锁**,否则抛出`IllegalMonitorStateException`[^4]。 - 同步块正是获取对象锁的唯一标准方式(`synchronized`或`ReentrantLock`)。 3. **线程状态转换的完整性** Java对象监视器要求线程状态转换遵循严格协议: ``` ┌─────────────┐ 获取锁 ┌─────────────┐ │ BLOCKED │ ────────────────> │ RUNNABLE │ └─────────────┘ └─────────────┘ ↑ │ wait() │ 释放锁后唤醒其他线程 ▼ ┌─────────────┐ ┌─────────────┐ │ WAITING │ <──────────────── │ RUNNABLE │ └─────────────┘ notify() └─────────────┘ ``` 若跳过同步块直接调用`wait()`,将破坏该状态机,导致不可预测行为。 --- ### 二、反例演示:非同步调用wait()的危险 ```java // 错误示例:wait()不在同步块中 public String take() throws InterruptedException { if (buffer.isEmpty()) { // 非原子检查! lock.wait(); // 可能永久等待或抛出异常 } return buffer.remove(); } ``` **风险分析**: 1. 当`buffer.isEmpty()`为`true`后,**线程切换**可能发生 2. 其他线程在`add()`中调用`notify()`(此时无等待线程,通知丢失) 3. 原线程执行`wait()`时**因未持有锁抛出`IllegalMonitorStateException`**[^4] --- ### 三、正确实现:同步块保障安全 ```java // 正确实现:同步块内检查条件+wait() public String take() throws InterruptedException { synchronized(lock) { // 获取对象锁 while (buffer.isEmpty()) { // 循环检查(防虚假唤醒) lock.wait(); // 原子:释放锁 → 进入等待 } return buffer.remove(); } // 自动释放锁 } ``` **同步块的作用**: 1. 🔒 锁内检查条件 → 阻塞其他线程修改状态 2. 🔄 `wait()`释放锁 → 允许其他线程进入同步块 3. 🔔 被`notify()`唤醒后 → **重新获取锁** → 再次检查条件[^3] --- ### 四、底层设计原理 Java对象头关联一个**监视器锁(Monitor)**,包含: - **Entry Set**:竞争锁的线程队列 - **Wait Set**:调用`wait()`的线程队列[^4] `wait()`的合法调用必须满足: $$ \text{调用线程} \in \text{Monitor Owner} \quad \text{(即持有锁的线程)} $$ 此约束由JVM在运行时通过**锁计数器**(`_recursions`)验证[^4]。 --- > 📌 **关键结论**:`wait()`的同步块约束本质是**线程安全三要素**的体现: > 1. **原子性**:条件检查与等待操作不可分割 > 2. **可见性**:锁保证共享状态修改对所有线程可见 > 3. **有序性**:监视器状态转换符合预期序列 --- ### 相关问题 1. 为什么`wait()`唤醒后要用`while`而非`if`检查条件? 2. `notifyAll()`和`notify()`在性能与安全性上有何权衡? 3. 如何处理`wait()`被调用时的**虚假唤醒**(Spurious Wakeup)? 4. `synchronized`与`ReentrantLock.Condition`的`await()`机制有何异同? [^1]: 确保线程同步、防止数据竞争、遵守语言规范 [^2]: 防止线程竞争,确保阻塞/唤醒安全 [^3]: wait()强制释放锁,调用前必须已持有锁 [^4]: 监视器锁模型要求原子状态转换 [^5]: 非同步调用wait()导致状态不一致
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值