第一部分:wait/notify基础

理解wait/notify在多线程通信中的核心作用

学习目标

掌握wait(), notify(), notifyAll()方法的使用场景和基本用法

wait()

使当前线程进入等待状态,直到其他线程调用notify()或notifyAll()方法

特点: 释放对象锁,线程进入WAITING状态

语法: object.wait()

notify()

唤醒在此对象监视器上等待的单个线程

特点: 随机唤醒一个等待线程

语法: object.notify()

notifyAll()

唤醒在此对象监视器上等待的所有线程

特点: 唤醒所有等待线程

语法: object.notifyAll()

1. 获取对象锁

线程进入同步代码块,获取对象锁

2. 检查条件

检查是否满足执行条件

3. 条件不满足,调用wait()

释放锁,线程进入等待状态

4. 其他线程调用notify()

唤醒等待线程

5. 重新获取锁

被唤醒的线程重新尝试获取锁

6. 再次检查条件

检查条件是否满足,继续执行

public class WaitNotifyExample { private final Object lock = new Object(); private boolean condition = false; public void waitMethod() throws InterruptedException { synchronized (lock) { // 使用while循环防止虚假唤醒 while (!condition) { System.out.println(Thread.currentThread().getName() + " 进入等待"); lock.wait(); // 释放锁,进入等待状态 } System.out.println(Thread.currentThread().getName() + " 被唤醒并执行"); // 执行后续操作 } } public void notifyMethod() { synchronized (lock) { condition = true; System.out.println(Thread.currentThread().getName() + " 发送通知"); lock.notify(); // 唤醒一个等待线程 // lock.notifyAll(); // 唤醒所有等待线程 } } }

关键注意事项

1. wait()和notify()必须在同步代码块内调用
2. 使用while循环检查条件,防止虚假唤醒
3. notify()随机唤醒一个线程,notifyAll()唤醒所有线程
4. 调用wait()后线程释放锁,进入WAITING状态

第二部分:wait/notify工作原理

深入理解wait/notify的底层机制

学习目标

掌握监视器锁、等待队列和状态转换机制

wait/notify工作原理示意图

wait/notify基于对象监视器实现线程通信

对象监视器

每个Java对象都有一个监视器锁,包含:

  • 锁状态(被哪个线程持有)
  • 等待队列(调用wait()的线程)
  • 入口队列(等待获取锁的线程)

状态转换

线程调用wait()后的状态变化:

  • RUNNABLE → WAITING
  • 释放对象锁
  • 进入等待队列
  • 被notify()唤醒后进入入口队列
  • 重新获取锁后进入RUNNABLE

虚假唤醒

线程可能在没有调用notify()的情况下被唤醒

原因: 操作系统调度、JVM实现

解决方案: 使用while循环检查条件

// 正确写法:使用while循环防止虚假唤醒 synchronized (lock) { while (!condition) { lock.wait(); } // 执行操作 }
线程1: 获取锁 → 检查条件 → 条件不满足 → 调用wait() → 释放锁 → 进入等待
线程2: 获取锁 → 执行操作 → 调用notify() → 释放锁
线程1: 被唤醒 → 尝试获取锁 → 获取锁 → 检查条件 → 执行操作

第三部分:生产者-消费者模式

使用wait/notify实现经典的生产者-消费者模式

学习目标

掌握使用wait/notify实现线程间协作的经典模式

import java.util.LinkedList; import java.util.Queue; public class ProducerConsumerExample { private final Queue queue = new LinkedList<>(); private final int capacity = 5; private final Object lock = new Object(); // 生产者线程 public void produce() throws InterruptedException { int value = 0; while (true) { synchronized (lock) { // 队列满时等待 while (queue.size() == capacity) { System.out.println("队列已满,生产者等待"); lock.wait(); } System.out.println("生产者生产: " + value); queue.add(value++); // 通知消费者 lock.notifyAll(); // 模拟生产耗时 Thread.sleep(500); } } } // 消费者线程 public void consume() throws InterruptedException { while (true) { synchronized (lock) { // 队列空时等待 while (queue.isEmpty()) { System.out.println("队列为空,消费者等待"); lock.wait(); } int value = queue.poll(); System.out.println("消费者消费: " + value); // 通知生产者 lock.notifyAll(); // 模拟消费耗时 Thread.sleep(1000); } } } public static void main(String[] args) { ProducerConsumerExample example = new ProducerConsumerExample(); // 创建生产者线程 Thread producerThread = new Thread(() -> { try { example.produce(); } catch (InterruptedException e) { e.printStackTrace(); } }); // 创建消费者线程 Thread consumerThread = new Thread(() -> { try { example.consume(); } catch (InterruptedException e) { e.printStackTrace(); } }); producerThread.start(); consumerThread.start(); } }
生产者生产: 0
生产者生产: 1
生产者生产: 2
生产者生产: 3
生产者生产: 4
队列已满,生产者等待
消费者消费: 0
生产者生产: 5
消费者消费: 1
生产者生产: 6
...

模式要点

1. 使用共享队列作为缓冲区
2. 生产者检查队列是否满,满则wait()
3. 消费者检查队列是否空,空则wait()
4. 生产后notifyAll()唤醒消费者
5. 消费后notifyAll()唤醒生产者

第四部分:常见错误与陷阱

避免wait/notify使用中的常见问题

1. 非同步块内调用

在非同步代码块中调用wait/notify

// 错误示例:非同步块内调用wait() public void method() { lock.wait(); // IllegalMonitorStateException }

正确做法:必须在synchronized代码块内调用

2. 使用if检查条件

使用if而不是while检查条件

// 错误示例:使用if检查条件 synchronized (lock) { if (!condition) { lock.wait(); // 可能虚假唤醒后不检查条件 } }

正确做法:使用while循环检查条件

3. 忘记notify

修改条件后忘记调用notify/notifyAll

// 错误示例:修改条件后忘记通知 synchronized (lock) { condition = true; // 修改条件 // 忘记调用notify/notifyAll }

正确做法:修改条件后调用notify/notifyAll

4. 错误使用notify

使用notify()但需要唤醒多个线程

// 错误示例:多个等待线程但只调用notify() synchronized (lock) { condition = true; lock.notify(); // 可能只唤醒一个线程,其他线程继续等待 }

正确做法:需要唤醒所有线程时使用notifyAll()

5. 忽略InterruptedException

忽略wait()抛出的InterruptedException

// 错误示例:忽略中断异常 try { lock.wait(); } catch (InterruptedException e) { // 忽略中断 }

正确做法:正确处理中断异常

6. 锁对象不一致

使用不同对象作为锁和wait/notify对象

// 错误示例:锁对象和wait对象不一致 Object lock1 = new Object(); Object lock2 = new Object(); synchronized (lock1) { lock2.wait(); // IllegalMonitorStateException }

正确做法:使用同一个对象作为锁和wait/notify对象

第五部分:wait/notify vs Condition

比较两种线程通信机制的差异

wait/notify

Java内置的线程通信机制

// wait/notify使用示例 synchronized (lock) { while (!condition) { lock.wait(); } // 执行操作 lock.notifyAll(); }

优点: 简单易用,无需额外依赖

缺点: 功能有限,只有一个等待队列

Condition

Lock接口提供的更灵活的通信机制

// Condition使用示例 Lock lock = new ReentrantLock(); Condition condition = lock.newCondition(); lock.lock(); try { while (!ready) { condition.await(); } // 执行操作 condition.signalAll(); } finally { lock.unlock(); }

优点: 多个等待队列,支持超时和中断

缺点: 使用稍复杂,需要手动释放锁

特性 wait/notify Condition
等待队列数量 1个 多个(可创建多个Condition)
超时等待 不支持 支持(await(long, TimeUnit))
中断响应 支持(抛出InterruptedException) 支持(awaitUninterruptibly())
锁机制 synchronized内置锁 Lock接口实现
使用复杂度 简单 稍复杂
适用场景 简单同步需求 复杂同步需求

选择建议

1. 简单场景使用wait/notify
2. 需要多个等待队列时使用Condition
3. 需要超时控制时使用Condition
4. 需要更细粒度控制时使用Condition

第六部分:wait/notify最佳实践

高效、安全地使用wait/notify机制

最佳实践

  • 使用while循环检查条件
  • 在同步块内调用wait/notify
  • 修改条件后调用notify/notifyAll
  • 使用private final对象作为锁
  • 正确处理InterruptedException

避免做法

  • 避免在非同步块内调用
  • 避免使用if检查条件
  • 避免忽略InterruptedException
  • 避免使用public对象作为锁
  • 避免不必要的notifyAll()调用

实践1:正确使用锁对象

  • 使用private final对象作为锁
  • 避免使用字符串常量或基本类型
  • 避免使用可能被修改的对象
// 推荐:使用private final对象 public class SafeExample { private final Object lock = new Object(); public void method() { synchronized (lock) { // ... } } }

实践2:优化通知机制

  • 优先使用notifyAll()
  • 仅在确定只唤醒一个线程时使用notify()
  • 在锁释放前调用notify/notifyAll
synchronized (lock) { // 修改条件 condition = true; // 在锁释放前通知 lock.notifyAll(); // 其他操作... } // 锁释放

实践3:处理中断

  • 正确处理InterruptedException
  • 恢复中断状态
  • 清理资源后退出
synchronized (lock) { try { while (!condition) { lock.wait(); } } catch (InterruptedException e) { // 恢复中断状态 Thread.currentThread().interrupt(); // 清理资源 return; } }

性能优化建议

1. 减少不必要的notifyAll()调用
2. 避免在循环中频繁调用wait/notify
3. 使用超时版本的wait(long timeout)
4. 合理设计条件检查逻辑
5. 避免长时间持有锁

第七部分:练习与测验

通过练习巩固wait/notify知识

动手练习

  • 实现一个线程安全的阻塞队列
  • 使用wait/notify实现多线程倒计时门闩
  • 实现一个简单的线程池使用wait/notify
  • 使用wait/notify实现读写锁
1

wait/notify知识测验

以下关于wait()方法的描述,哪项是正确的?
以下哪种情况会导致IllegalMonitorStateException?

深入学习资源

1. 《Java并发编程实战》第14章
2. Oracle官方文档:Object.wait()/notify()
3. Java并发编程之美:wait/notify机制
4. OpenJDK源码:ObjectMonitor实现