join()底层基于wait()实现线程等待:调用线程以目标线程对象为锁,检查isAlive(),若存活则wait()释放锁并进入WAITING状态;目标线程终止时JVM自动notifyAll()唤醒等待线程。

WAITING 状态不是“暂停执行”,而是“主动让出 CPU 并挂起等待”
当线程 A 执行 B.join() 时,A 并非简单地“卡住不动”,而是 JVM 主动将其状态设为 WAITING(JDK 官方定义:*indefinitely waiting for another thread to perform a particular action*)。这个动作背后是明确的线程调度决策:A 放弃当前 CPU 时间片,进入内核级等待队列,不参与调度竞争,也不消耗 CPU 资源。它只等一个信号——B 线程终止。
底层靠的是 B 对象上的 wait() + synchronized 锁配合
join() 是 synchronized 方法,A 在调用时会先尝试获取 B 线程对象的内部监视器锁(monitor lock)。一旦拿到锁,就立即检查 B.isAlive():
- 若 B 还活着 → A 调用 B.wait(0),立刻释放 B 的锁,并进入 WAITING 状态;
- 若 B 已结束(或根本没 start)→ 不 wait,方法直接返回,A 继续执行。
这个过程不是轮询、不是 sleep、不是 yield,而是基于 Java 对象监视器的原子性等待机制——判断存活与挂起必须在同一个临界区完成,否则可能漏掉 B 的终止事件。
B 结束那一刻,JVM 自动触发 notifyAll()
线程 B 的生命周期由 JVM 严格管理。当 B 的 run() 方法彻底退出、状态变为 TERMINATED 时,JVM 底层会自动在 B 对象上调用 notifyAll()(不是 B 线程自己调的,是 JVM runtime 行为)。所有因 B.join() 而在 B 上等待的线程(比如 A),都会被唤醒,重新争抢 B 的锁,随后退出 join 方法,恢复 RUNNABLE 状态。
为什么必须是 wait(),不能用 sleep() 或 busy-wait?
关键在于三点不可替代性:
- 释放锁:只有 wait() 会释放 B 对象的 monitor,允许 JVM 在 B 终止时安全地执行 notifyAll();
- 响应中断:wait() 可被 interrupt 中断并抛出 InterruptedException,sleep() 虽也可中断但无法感知目标线程状态变化;
- 零资源占用:WAITING 线程完全不占 CPU,而 busy-wait 会持续消耗算力,且无法精确感知 B 终止时机。
这种设计让 join() 成为轻量、可靠、可中断的同步原语,而非粗粒度的延时控制。

















