Thread.join()阻塞调用方线程而非子线程,需先start()再join(),支持超时与中断处理,多线程需逐个join,避免在同步块中调用以防死锁。

Thread.join() 会阻塞调用它的线程,不是子线程本身
很多人误以为 join() 是让子线程“停下来等”,其实它只影响调用方线程(比如主线程)。一旦主线程执行到 t.join(),就会暂停,直到线程 t 的 run() 方法彻底返回——哪怕子线程中途 sleep、IO 阻塞或抛异常,只要没结束,主线程就卡在这儿。
常见错误现象:join() 后主线程仍提前继续执行 → 很可能漏掉了对线程对象的引用,或者 start() 没调用就直接 join()(此时 join() 立即返回)。
- 必须先调用
t.start(),再调用t.join();t.run()不行,那只是普通方法调用,不启新线程 - 如果子线程已终止,
join()瞬间返回,不会阻塞 - 可传入毫秒超时参数:
t.join(5000),超时后主线程继续执行,但不表示子线程已结束
多个子线程要等全完成?别漏掉每个 join
一个 join() 只等一个线程。想等 3 个线程都跑完,就得对每个线程对象分别调用 join(),顺序无关,但必须全部调用,否则主线程可能在某个子线程还在跑时就往下走了。
典型使用场景:批量下载文件、并行计算分片结果、预热多个缓存 key。
- 不要写成
thread1.join(); thread2.start(); thread2.join();——thread2还没 start 就 join,等于白等 - 推荐先
start()所有线程,再统一join(),避免串行启动拖慢整体耗时 - 若用
ExecutorService,优先考虑shutdown() + awaitTermination(),更健壮
示例片段:
Thread t1 = new Thread(() -> { /* work1 */ });
Thread t2 = new Thread(() -> { /* work2 */ });
t1.start();
t2.start();
t1.join(); // 主线程停在这,直到 t1 结束
t2.join(); // 再停在这,直到 t2 结束join() 被中断时抛 InterruptedException,必须处理
如果主线程在 join() 期间被其他线程调用 interrupt(),会立刻抛出 InterruptedException,并清除中断状态。不捕获它会导致程序意外退出或逻辑断裂。
这不是“可选异常”——JVM 强制你面对中断语义。忽略它,等于屏蔽了线程协作的退出信号。
- 必须用
try-catch包住join(),不能只声明抛出 - 捕获后通常应恢复中断状态:
Thread.currentThread().interrupt(); - 若业务逻辑允许被中断(如等待超时后放弃),可在 catch 块里做清理并 return
错误写法:t.join(); // 编译不过;正确写法:
try {
t.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return; // 或 throw new RuntimeException(e);
}和 synchronized、wait/notify 混用时容易死锁
join() 底层用的是 wait(),所以它依赖对象监视器。如果在同步块里调用 join(),而子线程又恰好在同一个锁上执行了耗时操作甚至反向调用主线程,就可能形成锁等待闭环。
这种问题难复现、难调试,多出现在 GUI 或旧式事件驱动代码中(比如 Swing 的 Event Dispatch Thread 调用 join() 等后台线程)。
- 避免在
synchronized(obj)块内调用obj.join(),尤其当obj是共享锁对象时 - 子线程内部尽量不持有主线程正在等待的锁;如需通信,改用
CountDownLatch或CompletableFuture - Android 或 Swing 开发中,绝对不要在主线程
join()后台线程——UI 会卡死且无法响应中断
真正麻烦的不是语法,是线程之间隐含的锁依赖和执行时序。一旦涉及共享状态和等待,join() 就不再是简单的“等结束”,而是调度契约的一部分。

















