Java并发疑问:wait()与notify()为何需置于synchronized块中?
Java中wait()/notify()的核心疑问解答
嘿,这个问题绝对是Java并发入门里最容易卡壳的点之一,我来给你拆解明白~
先搞懂:为什么必须把wait()/notify()放在synchronized块里?
本质上是为了避免竞态条件,保证线程间状态的一致性。咱们拿你说的线程A和线程B的场景来举例:
假设线程A要等待某个条件(比如共享队列为空,需要等线程B放元素),如果没有synchronized保护,流程可能变成这样:
- 线程A检查队列,发现是空的
- 就在线程A准备调用wait()的瞬间,CPU切换到线程B,线程B往队列里放了元素,然后调用了notify()
- 这时候线程A才执行wait(),但它已经错过了刚才的notify,会一直阻塞下去——这就是典型的竞态条件,因为检查条件和wait()不是原子操作。
而synchronized块的作用,就是把“检查条件 + 调用wait()”或者“修改条件 + 调用notify()”变成一个原子操作:
- 线程A必须先获取锁,才能检查条件和调用wait(),这时候线程B根本没法修改条件,直到线程A调用wait()自动释放锁
- 线程B必须先获取同一个锁,才能修改条件和调用notify(),这时候线程A要么在wait(没持有锁),要么已经执行完了,不会出现刚才的“错过通知”情况
另外还有个关键:wait()调用时会自动释放锁,notify()调用后不会立刻释放锁——只有退出synchronized块才会释放。这也要求你必须先持有锁(也就是在synchronized块里),才能调用这两个方法,不然JVM会直接抛IllegalMonitorStateException。
再看:notify()到底该怎么执行?
其实notify()的执行逻辑和wait()是配套的,咱们还是用生产者消费者的代码例子来说明:
// 共享队列作为锁对象 class SharedQueue { private final Queue<String> queue = new LinkedList<>(); private static final int MAX_CAPACITY = 3; // 生产者方法 public synchronized void addItem(String item) throws InterruptedException { // 用while循环检查条件,防止虚假唤醒 while (queue.size() == MAX_CAPACITY) { wait(); // 释放锁,进入阻塞状态 } queue.add(item); System.out.println("生产者放入:" + item); notify(); // 唤醒一个在该锁上等待的线程 } // 消费者方法 public synchronized String takeItem() throws InterruptedException { while (queue.isEmpty()) { wait(); } String item = queue.poll(); System.out.println("消费者取出:" + item); notify(); return item; } }
notify()的执行步骤是这样的:
- 线程B(比如生产者)必须先获取锁对象的synchronized锁(也就是进入synchronized方法/块)
- 修改共享状态(比如往队列里加元素),让等待的线程A(消费者)的条件不再成立
- 调用
notify():这会唤醒一个在该锁对象上wait的线程(可能是线程A,也可能是其他等待的线程,取决于JVM调度) - 线程B继续执行synchronized块里的剩余代码,直到退出块,才会释放锁
- 被唤醒的线程A不会立刻执行,它会进入锁的等待队列,等获取到锁之后,才会从wait()的位置继续执行,并且必须重新检查条件(这就是为什么要用while而不是if,防止虚假唤醒——比如可能有其他线程又把队列填满了)
简单说:notify()只是“喊一声”,告诉等待的线程“条件可能变了”,但真正能继续执行,还得等当前线程释放锁,自己重新拿到锁才行。
内容的提问来源于stack exchange,提问作者DanielBK
相关产品推荐
相关产品推荐

