join方法只作用于已启动且未结束的thread实例,语义为当前线程阻塞直至该thread的run()执行完毕;runnable/callable无join方法,executorservice中需用future.get()或completablefuture.join()替代。

join方法的行为与线程创建方式无关,只与调用对象的生命周期有关。 无论你用继承Thread类、实现Runnable接口,还是使用Lambda表达式或Callable配合ExecutorService启动线程,只要是对某个Thread实例调用join(),它的语义始终是:当前线程阻塞,直到该Thread实例代表的线程**执行完毕(即run()方法返回)**。
什么是“被join的线程”
关键点在于:join操作的对象必须是一个已启动(start()已调用)、尚未结束的Thread实例。它不关心这个Thread是怎么构造的:
- 继承Thread并重写run() → 创建实例后调用start() → 可对其join
- new Thread(Runnable) → 启动后 → 可对其join
- new Thread(() -> {...}) → 启动后 → 同样可对其join
- 通过ExecutorService.submit(Callable)得到的是Future,不是Thread,不能直接join(需用get()等效等待)
常见误区:Runnable/Callable本身没有join
Runnable和Callable只是任务逻辑,它们不是线程。只有Thread实例才提供join方法。例如:
Runnable task = () -> { sleep(1000); };
Thread t = new Thread(task);
t.start();
t.join(); // ✅ 正确:对Thread实例调用
// task.join(); // ❌ 编译错误:Runnable没有join方法
join的阻塞逻辑与线程状态强相关
join是否真正阻塞,取决于目标线程的状态:
- 若目标线程已结束(TERMINATED),join立即返回
- 若目标线程未启动(NEW),join仍会等待——但该线程永远不会运行,除非你调用start();否则当前线程将无限等待
- 若目标线程正在运行或已进入BLOCKED/WAITING/TIMED_WAITING,join持续等待其自然结束
- 若目标线程被interrupt(),当前线程会抛出InterruptedException,并提前退出join
与ExecutorService协作时的替代方案
使用线程池时,通常不直接操作Thread实例。此时无法用join,但可用以下方式达到类似效果:
-
Future.get():阻塞等待任务完成(对应Runnable/Callable) -
CompletableFuture.join():无检异常的等待版本 - 若真需要获取底层Thread(极少必要),可通过自定义ThreadFactory保存引用,再对它join —— 但违背线程池设计初衷,不推荐
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











