一、基本机制与使用条件
-
方法定义与作用
wait()
:当前线程释放锁并进入等待池(Wait Set),等待被其他线程唤醒。notify()
:唤醒等待池中一个随机线程,使其进入锁池(Lock Queue),重新竞争锁。notifyAll()
:唤醒等待池中所有线程,均进入锁池竞争锁。
-
使用要求
- 必须在同步代码块或方法中调用,否则抛出
IllegalMonitorStateException
。 - 调用者必须是当前同步块的锁持有者(即对象的监视器)。
- 必须在同步代码块或方法中调用,否则抛出
二、实现流程与线程状态转换
-
线程调用
wait()
的流程- 线程执行
wait()
后,释放锁,进入等待池。 - 线程状态从RUNNABLE变为WAITING(或
TIMED_WAITING
,若调用带超时的wait(long)
)。 - 其他线程调用
notify()
或notifyAll()
后,等待池中的线程被唤醒,进入锁池,等待重新获取锁。
- 线程执行
-
唤醒与锁竞争
- 唤醒的线程需重新通过同步锁的竞争机制(如CAS操作)获取锁,成功后进入RUNNABLE状态继续执行。
- 若锁竞争失败,线程可能再次进入锁池等待。
三、关键注意事项与最佳实践
-
循环条件检查
- 避免虚假唤醒:
wait()
可能因系统中断或非预期事件被唤醒,需在循环中检查条件,而非单次判断。
。synchronized (lock) { while (conditionNotMet) { lock.wait(); // 循环检查条件 } // 执行后续操作 }
- 避免虚假唤醒:
-
优先使用
notifyAll()
notify()
可能仅唤醒一个线程,若该线程无法满足后续条件(如生产者线程被唤醒但队列未空),可能导致其他线程饥饿。notifyAll()
确保所有等待线程重新竞争锁,避免遗漏唤醒。
-
异常处理
wait()
可能抛出InterruptedException
,需在try-catch
块中处理,避免线程意外终止。
四、典型应用场景
-
生产者-消费者模型
- 生产者:在队列满时调用
wait()
,队列有空间时notifyAll()
唤醒消费者。 - 消费者:在队列空时调用
wait()
,队列有数据时notifyAll()
唤醒生产者。 - 代码示例(基于文章1):
public synchronized void produce(T item) throws InterruptedException { while (queue.size() == maxSize) { // 循环检查条件 wait(); } queue.add(item); notifyAll(); // 唤醒所有等待线程 }
- 生产者:在队列满时调用
-
交替打印数字
- 线程协作:一个线程打印奇数,另一个打印偶数,通过
notify()
交替唤醒。 - 代码示例(基于文章9):
。public void run() { synchronized (this) { notify(); // 唤醒另一个线程 while (i <= 100) { System.out.println[i++]); try { wait(); // 当前线程等待 } catch (InterruptedException e) { ... } } } }
- 线程协作:一个线程打印奇数,另一个打印偶数,通过
五、与synchronized
的协同作用
- 锁的释放与重新获取:
wait()
释放锁后,其他线程可进入同步块修改共享资源;唤醒后需重新获取锁才能继续执行。 - 条件变量:通过
wait()
和notify()
实现多条件协作,而synchronized
仅提供基础的互斥机制。
六、总结
wait()
、notify()
和notifyAll()
通过锁机制实现线程间的高效协作,其核心在于条件检查、锁释放与唤醒的协同。开发者需注意循环检查条件、优先使用notifyAll()
,并结合synchronized
确保线程安全。正确应用这些方法可解决生产者-消费者、任务调度等复杂场景的同步问题。