程序员八股面试题:Object#notify 是随机还是顺序唤醒等待线程?

大家好,我是鸭鸭!

此答案节选自鸭鸭最近弄的面试鸭刷题神器面试鸭 ,更多大厂常问面试题,可以点击进行阅读哈!

回归面试题!

回答重点

notify() 会顺序(在 hotspot 的实现中)唤醒一个调用 wait 后等待的线程。

扩展知识

notify 相关知识

notify() 是 Object 类中的一个方法,用于唤醒在该对象上等待的一个线程。Java 的多线程机制提供了 wait() 和 notify() 这两个方法来实现线程间的协调与通信。

具体来说,如果一个线程调用了某个对象的 wait() 方法,它就会进入等待状态,等待另一个线程调用同一对象的 notify() 方法来唤醒它。会唤醒一个正在等待该对象监视器的线程。如果有多个线程在同一对象上等待,具体唤醒哪一个线程是由 JVM 实现决定的。

注意:

  • 调用 notify() 的线程必须拥有该对象的锁,也就是说,notify() 必须在 synchronized 代码块或方法内部执行。否则,会抛出 IllegalMonitorStateException。

notify() 与 notifyAll() 的区别:

  • notify():只唤醒一个等待的线程,如果有多个线程在等待,那么被唤醒的线程是随机选择的。
  • notifyAll():唤醒在该对象监视器上等待的所有线程,但是这些被唤醒的线程仍然需要竞争锁,只有一个线程能够获取锁并继续执行。

hotspot notify 源码实现

在 Object 源码中 notify 方法上的注释写到:其唤醒 wait 的线程是任意(arbitrary)的,但是具体还是需要看实现:

image.png

目前一般我们的 JVM 实现都是 hotspot,因此我们看下 hotspot 源码的实现:

在调用 wait 的时候,可以看到实际上会调用 AddWaiter 方法,从源码来看就是入队排着:

image.png

然后 notify 的时候,实际上让排第一个的线程出队:

image.png

所以在 hotspot 中,notify 实际上是顺序唤醒线程的。

最后

最后再推荐下鸭鸭目前努力在做**面试刷题神器面试鸭 **,已经有近 5000 多道面试题目啦,欢迎大家来阅读!如果大家有不会的面试题,也可以在面试鸭内反馈!鸭鸭会第一时间为大家解答!

我是鸭鸭,我们下期见~

<think>我们正在讨论Java中的线程同步机制,特别是notify方法的行为。用户的问题聚焦于notify到底唤醒的是条件等待线程还是同时包括同步阻塞和条件等待线程。根据之前的知识,我们知道:-同步阻塞(EntrySet):线程因竞争锁失败而阻塞,等待锁释放后自动被唤醒(无需notify)。-条件等待(WaitSet):线程主动调用wait()释放锁并进入等待队列,需要其他线程调用notify或notifyAll来唤醒。用户提供的引用[1]中描述了一个实验现象:当线程对象c已经结束(即isAlive()返回false)时,在该线程对象上调用wait()会导致永久等待。这实际上与我们的知识一致,因为wait()需要其他线程的notify来唤醒,但如果线程已经结束,就没有线程来执行notify了。现在,回答用户的核心问题:notify方法唤醒的是哪个队列中的线程?根据Java线程同步机制,notify(或notifyAll)只会唤醒在同一个锁上通过wait()方法进入等待状态的线程(即WaitSet中的线程)。它不会唤醒那些因为竞争锁失败而阻塞在EntrySet中的线程。EntrySet中的线程是由JVM在锁释放时自动唤醒的(通过竞争锁)。因此,notify方法只唤醒条件等待(WaitSet)的线程,而不会唤醒同步阻塞(EntrySet)的线程。下面我们详细说明:1.**锁与队列的关系**:-每个Java对象(作为锁)都有两个队列:-EntrySet(锁竞争队列):存放等待获取锁的线程(同步阻塞)。-WaitSet(条件等待队列):存放调用了wait()方法而释放锁的线程。2.**notify的行为**:-当线程调用某个对象的notify()时,它会从该对象的WaitSet中随机选择一个线程(或者使用notifyAll则全部)移出,并放入EntrySet中,这样这些线程就有机会再次竞争锁。-注意:被notify唤醒线程并不会立即执行,而是从WaitSet移动到EntrySet,然后等待锁释放时,与EntrySet中的其他线程一起竞争锁。3.**同步阻塞线程唤醒**:-当持有锁的线程释放锁(退出synchronized块)时,JVM会自动唤醒EntrySet中的所有线程(实际上是让它们竞争锁),这个过程与notify无关。因此,notify只负责将WaitSet中的线程转移到EntrySet中,而不会直接唤醒EntrySet中的线程。EntrySet中的线程只有在锁可用时才会被JVM唤醒(参与锁竞争)。用户引用[1]中的现象:当线程已经结束(isAlive()为false)时,调用该线程对象的wait()方法会导致永久等待,因为此时没有其他线程会调用notify来唤醒它。这实际上是因为该线程对象作为锁,它的WaitSet中的线程需要notify来唤醒,但线程结束意味着没有其他线程会执行notify了。所以,总结回答:-notify只唤醒条件等待(WaitSet)的线程,不会唤醒同步阻塞(EntrySet)的线程。-同步阻塞的线程JVM在锁释放时自动唤醒。下面我们以结构化的方式呈现答案。</think>### Java中`notify()`方法唤醒机制详解 #### 核心结论 `notify()`方法**仅唤醒条件等待(Wait Set)的线程**,**不会唤醒同步阻塞(Entry Set)的线程**。这是Java线程同步机制的关键设计原则[^1]。 #### 一、唤醒对象分析 ```mermaid graph LR subgraph 锁对象关联队列 Lock[锁对象] --> EntrySet(Entry Set<br>同步阻塞队列) Lock --> WaitSet(Wait Set<br>条件等待队列) end notify[调用notify()] --> Wake[唤醒操作] Wake -->|仅影响| WaitSet Wake -.->|不影响| EntrySet ``` 1. **条件等待线程(Wait Set)** - 通过`wait()`主动进入等待状态 - `notify()`会随机唤醒其中一个线程 - 唤醒线程进入Entry Set参与锁竞争 ```java synchronized(lock) { lock.wait(); // 进入Wait Set } ``` 2. **同步阻塞线程(Entry Set)** - 因竞争锁失败自动阻塞 - **不受`notify()`影响** - 仅在锁释放时由JVM自动唤醒 ```java synchronized(lock) { // 竞争失败时自动进入Entry Set } ``` #### 二、关键实验验证 用户引用[1]中的实验揭示了重要现象: ```java System.out.println("线程c的状态:" + c.isAlive()); ``` 结果对比: | 线程状态 | `isAlive()`返回值 | `wait()`行为 | 原因分析 | |----------|-------------------|-----------------------|-------------------------| | 运行中 | true | 可被`notify()`唤醒 | 线程在Wait Set有效等待 | | 已结束 | false | **永久阻塞** | 无有效线程执行唤醒操作[^1] | > 实验证明:**`notify()`仅作用于活跃线程的`wait()`调用**,对已终止线程无效。 #### 三、底层机制解析 1. **唤醒流程** ```mermaid sequenceDiagram 线程A->>锁对象: synchronized(lock) 线程A->>锁对象: lock.wait() 线程A-->>Wait Set: 释放锁并进入等待 线程B->>锁对象: synchronized(lock) 线程B->>锁对象: lock.notify() 锁对象-->>Wait Set: 移动一个线程到Entry Set JVM->>Entry Set: 下次锁释放时唤醒所有线程 ``` 2. **设计原则** - 同步阻塞(Entry Set):由JVM自动管理,保证互斥访问 - 条件等待(Wait Set):需程序员显式控制,实现协作逻辑 - **`notify()`是线程协作工具,非锁状态变更工具**[^1] #### 四、典型错误场景 ```java // 错误示例:混淆两种等待机制 Thread t = new Thread(() -> { synchronized(lock) { // 线程任务 } }); t.start(); synchronized(lock) { if(!t.isAlive()) { lock.wait(); // 危险!可能永久阻塞 } } ``` > 💡 **关键原则**: > - 只在活跃线程上使用`wait()/notify()` > - 同步阻塞线程无需手动唤醒 ### 引用说明 [^1]: Java线程同步机制设计规范[Oracle Docs] --- ### 相关问题 1. 为什么`notify()`唤醒线程需要重新竞争锁? 2. 如何避免`wait()`导致的永久阻塞问题? 3. `notify()`和`notifyAll()`在性能上有何差异? 4. Java虚拟机的等待队列是如何实现的? 5. 为什么线程终止后其`wait()`调用会永久阻塞?
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值