一、核心概念与机制
1. 监视器锁(Monitor Lock)
graph LR A[对象头] --> B[Mark Word] B --> C[锁状态标志] C --> D[01: 无锁] C --> E[00: 轻量级锁] C --> F[10: 重量级锁] C --> G[11: GC标记]
每个 Java 对象都与一个监视器锁关联,这是 wait()
和 notify()
工作的基础:
- 进入同步块时自动获取锁
- 退出同步块时自动释放锁
- 锁存储在对象头的 Mark Word 中
2. wait-notify 工作原理
sequenceDiagram participant T1 as 线程1 participant Lock as 对象锁 participant T2 as 线程2 T1->>Lock: synchronized获取锁 T1->>Lock: wait()释放锁 Note over T1: 进入WAITING状态 T2->>Lock: synchronized获取锁 T2->>Lock: notify()/notifyAll() Lock-->>T1: 收到通知 Note over T1: 尝试重新获取锁 T2->>Lock: 退出同步块释放锁 T1->>Lock: 成功获取锁 Note over T1: 从wait()返回继续执行
二、方法详解
1. wait() 方法家族
// 无限期等待(常用)
public final void wait() throws InterruptedException;
// 带超时的等待
public final void wait(long timeoutMillis) throws InterruptedException;
// 带纳秒精度的超时
public final void wait(long timeoutMillis, int nanos) throws InterruptedException;
2. notify() 与 notifyAll()
// 随机唤醒一个等待线程
public final void notify();
// 唤醒所有等待线程
public final void notifyAll();
三、标准使用模式
1. 生产者-消费者经典实现
class SharedBuffer {
private final Queue<Integer> queue = new LinkedList<>();
private final int capacity;
public SharedBuffer(int capacity) {
this.capacity = capacity;
}
public synchronized void produce(int item) throws InterruptedException {
// 使用while避免虚假唤醒
while (queue.size() == capacity) {
wait(); // 缓冲区满,等待
}
queue.add(item);
System.out.println("生产: " + item + " 缓冲区大小: " + queue.size());
notifyAll(); // 通知消费者
}
public synchronized int consume() throws InterruptedException {
while (queue.isEmpty()) {
wait(); // 缓冲区空,等待
}
int item = queue.poll();
System.out.println("消费: " + item + " 缓冲区大小: " + queue.size());
notifyAll(); // 通知生产者
return item;
}
}
2. 条件等待模板
synchronized (lockObject) {
while (!condition) { // 必须使用while循环
lockObject.wait();
}
// 条件满足后的操作
}
四、关键注意事项
1. 必须持有锁
- 调用
wait()
或notify()
前必须获取对象的监视器锁 - 违反规则会抛出
IllegalMonitorStateException
2. 虚假唤醒(Spurious Wakeup)
- 即使没有通知,线程也可能从
wait()
返回 - 解决方案:始终在循环中检查等待条件
// 错误:使用if检查条件
if (!condition) {
wait();
}
// 正确:使用while检查条件
while (!condition) {
wait();
}
3. notify() vs notifyAll()
场景 | 推荐方法 | 原因 |
---|---|---|
所有等待线程类型相同 | notify() |
更高效,减少线程竞争 |
多种类型等待线程 | notifyAll() |
避免错误唤醒 |
复杂条件 | notifyAll() |
确保正确线程被唤醒 |
性能敏感场景 | notify() |
减少上下文切换开销 |
五、高级应用模式
1. 多条件等待
class MultiConditionBuffer {
private final Object fullLock = new Object();
private final Object emptyLock = new Object();
public void produce() throws InterruptedException {
synchronized (fullLock) {
while (isFull()) {
fullLock.wait();
}
// 生产逻辑...
}
synchronized (emptyLock) {
emptyLock.notifyAll();
}
}
public void consume() throws InterruptedException {
synchronized (emptyLock) {
while (isEmpty()) {
emptyLock.wait();
}
// 消费逻辑...
}
synchronized (fullLock) {
fullLock.notifyAll();
}
}
}
2. 超时控制
public synchronized boolean tryConsume(long timeout) throws InterruptedException {
long endTime = System.currentTimeMillis() + timeout;
long remaining = timeout;
while (queue.isEmpty() && remaining > 0) {
wait(remaining);
remaining = endTime - System.currentTimeMillis();
}
if (!queue.isEmpty()) {
consumeItem();
return true;
}
return false; // 超时未消费
}
六、常见错误与解决方案
错误类型 | 示例 | 解决方案 |
---|---|---|
未持有锁调用 | obj.wait() 不在同步块内 |
确保在 synchronized 块内调用 |
使用if而非while | if(condition) wait() |
改为 while(condition) wait() |
忽略中断异常 | 不处理 InterruptedException |
合理处理中断或传播异常 |
notify错误对象 | 对A对象wait,对B对象notify | 确保使用同一个对象 |
丢失唤醒 | 条件检查在notify之后 | 正确同步条件检查和通知 |
七、性能优化技巧
1. 减少锁竞争
// 使用分离锁优化
class OptimizedBuffer {
private final Object putLock = new Object();
private final Object takeLock = new Object();
// 生产消费逻辑分离...
}
2. 条件通知
public synchronized void produce(int item) {
boolean wasEmpty = queue.isEmpty();
// 生产逻辑...
if (wasEmpty) {
notifyAll(); // 仅当从空变为非空时通知
}
}
3. 使用 Lock/Condition API
import java.util.concurrent.locks.*;
class AdvancedBuffer {
private final Lock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();
public void put(int item) throws InterruptedException {
lock.lock();
try {
while (isFull()) {
notFull.await();
}
// 添加元素
notEmpty.signal(); // 精确通知消费者
} finally {
lock.unlock();
}
}
}
八、替代方案比较
方案 | 适用场景 | 优势 | 劣势 |
---|---|---|---|
wait()/notify() |
简单同步需求 | 内置于Object,无需额外依赖 | 功能有限,易出错 |
Lock/Condition |
复杂同步需求 | 支持多个条件队列,更灵活 | 需要显式锁管理 |
BlockingQueue |
生产者-消费者模式 | 开箱即用,线程安全 | 定制性较差 |
Semaphore |
资源访问控制 | 控制并发访问数量 | 不直接支持条件等待 |
CountDownLatch |
一次性事件等待 | 简单等待多个线程完成 | 不可重用 |
CyclicBarrier |
多阶段任务同步 | 可重用,支持屏障操作 | 实现复杂 |
九、最佳实践总结
遵循标准模式:
- 始终在循环中调用
wait()
- 条件检查与通知使用同一个锁对象
- 始终在循环中调用
优先使用 notifyAll():
- 除非在性能关键路径且能确保正确性
- 在不确定时使用
notifyAll()
更安全
处理中断:
try { while (condition) { wait(); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); // 恢复中断状态 // 处理中断逻辑 }
避免嵌套监视器锁死:
- 不要在不同的对象上嵌套同步
- 使用
ReentrantLock
替代synchronized
性能敏感场景优化:
- 使用分离锁
- 实现条件通知
- 考虑无锁数据结构
新项目推荐:
- 优先使用
java.util.concurrent
包中的高级工具 - 在需要精细控制时使用
Lock
和Condition
- 优先使用
十、真实场景案例
数据库连接池实现
class ConnectionPool {
private final List<Connection> pool = new ArrayList<>();
private final int maxSize;
public ConnectionPool(int maxSize) {
this.maxSize = maxSize;
// 初始化连接...
}
public synchronized Connection getConnection(long timeout)
throws InterruptedException, TimeoutException {
long endTime = System.currentTimeMillis() + timeout;
long remaining = timeout;
while (pool.isEmpty() && remaining > 0) {
wait(remaining);
remaining = endTime - System.currentTimeMillis();
}
if (!pool.isEmpty()) {
return pool.remove(0);
}
throw new TimeoutException("获取连接超时");
}
public synchronized void releaseConnection(Connection conn) {
pool.add(conn);
notifyAll(); // 通知等待线程
}
}
关键点:
wait()
/notify()
是 Java 线程同步的基石,但在现代 Java 开发中,应优先考虑java.util.concurrent
包中的高级工具。理解这些底层机制对于诊断复杂并发问题和理解高级 API 的工作原理至关重要。