Java中Object中notify和wait方法线程同步原理

总结摘要
Java中Object中notify和wait方法线程同步原理

核心概念:监视器锁(Monitor)

Java 中每个对象都天然绑定一把监视器锁(Monitor / intrinsic lock)wait / notify / notifyAll 正是基于这把锁来协调线程之间的通信。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
┌─────────────────────────────────────────┐
              Object Monitor             
                                         
  ┌──────────────┐   ┌────────────────┐  
    Entry Set         Wait Set       
  wait get lock     exec wait func   
  └──────┬───────┘   └───────┬────────┘  
                             notify    
                                       
      ┌──────────────────────────┐       
          Owner Thread                 
      └──────────────────────────┘       
└─────────────────────────────────────────┘

三个核心方法

方法作用
wait()释放锁,当前线程进入该对象的 Wait Set,挂起等待
notify()从 Wait Set 中随机唤醒一个线程,移入 Entry Set
notifyAll()唤醒 Wait Set 中的全部线程,移入 Entry Set

⚠️ 前提:这三个方法必须在 synchronized 块内调用,否则抛出 IllegalMonitorStateException


详细执行流程

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
Thread-A (生产者)                Thread-B (消费者)
─────────────────                ─────────────────
synchronized(lock) {             synchronized(lock) {
  // 1. 获得锁                     // 1. 尝试获得锁(阻塞在 Entry Set)
  while(!条件满足) {               
    lock.wait();  ──────────────► // 2. A 释放锁,进入 Wait Set
  }                               //    B 获得锁,执行业务
  // 5. 被唤醒后                   
  //    重新竞争锁                  lock.notifyAll(); ─► 3. 唤醒 Wait Set 线程
  // 6. 获锁后继续执行              } // 4. B 退出 synchronized,释放锁
}

逐步拆解 wait() 的内部动作

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
调用 wait()
    ├─ 1. 检查当前线程是否持有该对象的 Monitor → 否则抛异常
    ├─ 2. 将当前线程加入对象的 Wait Set
    ├─ 3. 原子性地 释放 Monitor 锁
    ├─ 4. 线程挂起(操作系统层面 park)
    │         ↑ 此处等待被唤醒
    ├─ 5. 收到 notify / 超时 / interrupt → 移出 Wait Set
    ├─ 6. 重新竞争 Monitor 锁(可能再次阻塞)
    └─ 7. 获锁成功 → wait() 返回,继续执行

经典范例:生产者 - 消费者

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class Buffer {
    private final Queue<Integer> queue = new LinkedList<>();
    private final int CAPACITY = 5;

    // 生产者
    public synchronized void produce(int item) throws InterruptedException {
        // ✅ 用 while 而不是 if,防止虚假唤醒(spurious wakeup)
        while (queue.size() == CAPACITY) {
            System.out.println("缓冲区满,生产者等待...");
            wait(); // 释放锁,进入 Wait Set
        }
        queue.offer(item);
        System.out.println("生产: " + item);
        notifyAll(); // 唤醒所有等待的消费者
    }

    // 消费者
    public synchronized int consume() throws InterruptedException {
        while (queue.isEmpty()) {
            System.out.println("缓冲区空,消费者等待...");
            wait(); // 释放锁,进入 Wait Set
        }
        int item = queue.poll();
        System.out.println("消费: " + item);
        notifyAll(); // 唤醒所有等待的生产者
        return item;
    }
}

关键细节与常见陷阱

1. 必须用 while 而不是 if

1
2
3
4
5
// ❌ 错误:if 只判断一次,可能存在虚假唤醒
if (!condition) { lock.wait(); }

// ✅ 正确:循环检查条件
while (!condition) { lock.wait(); }

原因:线程被唤醒后重新竞争锁,在竞争期间条件可能被其他线程改变(虚假唤醒)。

2. notify vs notifyAll

1
2
notify()    → 只唤醒1个 → 如果唤错线程,可能死锁
notifyAll() → 唤醒所有  → 更安全,性能略低

一般优先使用 notifyAll(),除非你能确保所有等待线程等待同一个条件。

3. wait() 会释放锁,sleep() 不会

1
2
3
4
5
// wait:释放锁,让其他线程进入
synchronized(obj) { obj.wait(); }  // ✅ 释放 obj 的锁

// sleep:持锁睡觉,其他线程无法进入
synchronized(obj) { Thread.sleep(1000); } //  仍然持有锁

底层实现(JVM 层)

1
2
3
4
Java               JVM  (C++)           OS 
─────────            ──────────────         ──────
object.wait()    ObjectMonitor::wait()    pthread_cond_wait()
object.notify()  ObjectMonitor::notify()  pthread_cond_signal()
  • HotSpot JVM 中由 ObjectMonitor 类实现,内部维护 _WaitSet(等待集)和 _EntryList(竞争队列)
  • 最终依赖操作系统的**条件变量(condition variable)**机制

java.util.concurrent 的对比

wait/notifyCondition(Lock配套)
绑定Object 对象显式 Lock
等待队列一个可多个(更精细控制)
中断响应支持支持
超时等待wait(timeout)await(timeout, unit)
推荐场景简单同步复杂并发控制

现代 Java 开发中推荐使用 ReentrantLock + Condition,但理解 wait/notify 是掌握 Java 并发的基础。