Thread.join能阻塞主线程是因为它使当前线程进入WAITING状态,依赖JVM底层通知机制等待目标线程终止;必须在start()后调用,否则立即返回;支持超时等待和中断响应。

Thread.join 为什么能阻塞主线程
Thread.join 的本质是让当前线程(比如主线程)进入 WAITING 状态,直到目标线程终止。它不是轮询,也不占用 CPU,而是依赖 JVM 底层的线程状态通知机制。调用后,主线程会挂起,真正等到子线程执行完 run() 方法并自然退出,或因异常提前结束,join() 才返回。
注意:如果子线程被 interrupt() 中断,join() 会立即抛出 InterruptedException 并退出 —— 这不是“没等完”,而是被主动打断了。
必须在 start() 之后调用 join(),否则无效
常见错误是先 join() 再 start(),或者对未启动的线程调用 join()。此时 join() 会立刻返回,因为线程状态仍是 NEW,根本没在运行。
- ✅ 正确顺序:
t.start(); t.join(); - ❌ 错误写法:
t.join(); t.start();(无等待效果) - ❌ 错误写法:
new Thread(() -> {}).join();(线程从未 start,状态一直是 NEW)
可以加一句 if (t.getState() == Thread.State.NEW) throw new IllegalStateException("Thread not started"); 提前兜底。
带超时的 join(long millis) 要小心时间单位和返回逻辑
join(5000) 表示“最多等 5 秒”,但不保证一定等到 5 秒 —— 如果子线程 2 秒就结束了,join() 立刻返回;如果 5 秒后还没结束,它也会返回,此时子线程仍在后台运行。
- 超时后无法判断子线程是否还活着,得靠
t.isAlive()检查 -
join(0)等价于无参join(),表示无限等待 - 毫秒值为负数会直接抛
IllegalArgumentException - 系统负载高时,实际等待时间可能略超设定值(调度延迟),但不会提前返回
示例:
Thread t = new Thread(() -> { try { Thread.sleep(3000); } catch (InterruptedException e) {} });<br>t.start();<br>t.join(2000); // 返回时 t.isAlive() == true<br>System.out.println(t.isAlive()); // true
多个子线程要逐个 join,不能只 join 最后一个
主线程调用 t2.join() 不会影响 t1 的生命周期。如果你启动了 t1 和 t2,又只对 t2 调用 join(),那么 t1 可能在主线程继续执行时还在跑,甚至已提前结束 —— 主线程对此一无所知。
- 要确保所有子线程都完成,就得显式对每个线程调用
join() - 顺序无关(
t1.join(); t2.join();和反过来效果一样),但建议按启动顺序写,便于排查 - 如果线程数量多,可存入
List,再遍历调用join()
容易忽略的一点:如果某个 join() 抛出 InterruptedException,后续的 join() 就不会执行 —— 必须在 catch 块里决定是否重置中断状态或继续等待其他线程。










