join()底层本质是基于对象监视器的wait/notify机制:调用线程以目标线程对象为锁,循环检查isAlive(),若存活则wait()释放锁并挂起;目标线程终止时JVM自动notifyAll()唤醒等待线程。

join() 的底层原理,本质是用 wait() 实现的线程等待逻辑。它不是靠轮询或忙等待,而是基于 Java 对象监视器(monitor)的 wait/notify 机制,让调用线程安全、高效地挂起并等待目标线程结束。
同步锁与 wait 的配合
当线程 A 调用线程 B 的 join() 时,JVM 会:
- 以线程 B 的实例对象为锁,进入 synchronized 块(
join()方法被声明为synchronized) - 反复检查
B.isAlive()—— 若 B 还在运行,就调用B.wait() -
B.wait()会立即释放 B 对象上的锁,并使线程 A 进入 WAITING 状态,不再占用 CPU - 一旦线程 B 执行完毕(进入 TERMINATED 状态),JVM 底层会自动触发
B.notifyAll(),唤醒所有在 B 上等待的线程(比如 A)
为什么是 wait(),而不是 sleep() 或 yield()?
因为只有 wait() 具备三个关键能力:
- 必须在同步上下文中调用,能配合对象锁确保状态判断(
isAlive())和等待动作的原子性 - 主动释放锁,避免死锁,也允许其他线程修改共享状态(如线程生命周期标记)
- 可被精准唤醒(由 JVM 在线程终止时自动通知),无需猜测休眠时长,也不依赖轮询
超时版本 join(long millis) 的特殊处理
带超时参数的 join 并非简单地调用 wait(millis) 后继续,而是:
- 每次
wait(delay)返回后,重新计算剩余等待时间 - 若剩余时间 ≤ 0,跳出循环,不再等待
- 若线程 B 在等待中提前结束,
isAlive()变为 false,循环直接退出 - 这样既支持“等够时间就走”,也支持“B 一结束立刻响应”,兼顾灵活性与及时性
关键注意事项
理解底层原理后,这几个点容易出错:
-
join()阻塞的是调用它的线程,不是被 join 的线程 —— t1.join() 是让当前线程等 t1,t1 自己照常运行 - 必须在目标线程已 start() 之后调用 join(),否则
isAlive()为 false,方法直接返回,不等待 - 所有 join() 重载都抛出
InterruptedException,因为 wait() 可被中断;捕获后通常需恢复中断状态(Thread.currentThread().interrupt()) -
join(0)等价于无参join(),表示无限等待,不是“等 0 毫秒”


















