一、核心概念与机制

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 多阶段任务同步 可重用,支持屏障操作 实现复杂

九、最佳实践总结

  1. 遵循标准模式

    • 始终在循环中调用 wait()
    • 条件检查与通知使用同一个锁对象
  2. 优先使用 notifyAll()

    • 除非在性能关键路径且能确保正确性
    • 在不确定时使用 notifyAll() 更安全
  3. 处理中断

    try {
        while (condition) {
            wait();
        }
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt(); // 恢复中断状态
        // 处理中断逻辑
    }
    
  4. 避免嵌套监视器锁死

    • 不要在不同的对象上嵌套同步
    • 使用 ReentrantLock 替代 synchronized
  5. 性能敏感场景优化

    • 使用分离锁
    • 实现条件通知
    • 考虑无锁数据结构
  6. 新项目推荐

    • 优先使用 java.util.concurrent 包中的高级工具
    • 在需要精细控制时使用 LockCondition

十、真实场景案例

数据库连接池实现

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 的工作原理至关重要。