join() 是最直接可靠的线程串行执行方式,通过阻塞等待前一线程终止来确保顺序,无需锁或共享变量,且必须在 start() 后调用;sleep() 不可靠,wait()/notify() 和 CountDownLatch 属过度设计;真实项目中推荐单线程池+Future。

join() 是最直接、最常用的方式,确保 T2 在 T1 完成后执行、T3 在 T2 完成后执行——不需要锁、不依赖共享变量,靠线程生命周期控制即可。
用 join() 实现串行执行最稳妥
多个线程按顺序执行,本质是“等待前一个结束再启动下一个”,join() 正是为此设计:调用 t1.join() 会让当前线程(比如主线程)阻塞,直到 t1 终止。
- 必须在
start()之后调用join(),否则无效(未启动的线程调join()立即返回) - 不能在
run()方法里对自身调join()(会死等) - 若 T1 抛异常提前退出,
join()仍会返回,T2 仍会启动——它只等“终止”,不区分正常/异常结束
Thread t1 = new Thread(() -> System.out.println("T1 done"));
Thread t2 = new Thread(() -> System.out.println("T2 done"));
Thread t3 = new Thread(() -> System.out.println("T3 done"));
t1.start();
try { t1.join(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
t2.start();
try { t2.join(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
t3.start();
别用 sleep() 模拟顺序执行
sleep(1000) 看似简单,但实际不可靠:T1 可能 50ms 就跑完,也可能因 GC、调度延迟卡到 2s;硬编码休眠时间既不精确,又浪费资源。
-
sleep()不释放锁,也不感知目标线程状态,纯靠“猜时间”——这在面试中会被立刻追问“如果 T1 执行超时怎么办?” - 一旦涉及 I/O、计算密集或异常分支,
sleep()方案必然失效
为什么不用 wait()/notify() 或 CountDownLatch?
它们能实现,但属于“过度设计”:对于简单的三线程串行,引入锁、监视器或计数器,反而增加出错风险(比如漏 notify、latch 未 countDown、中断处理缺失)。
-
CountDownLatch(1)可行,但需额外对象通信、手动countDown(),比join()多两步且易忘 -
wait()/notify()要求同步块、必须持有同一把锁,稍不注意就抛IllegalMonitorStateException - 面试官问这个题,本意是考察你对线程基本协作机制的理解深度,而非炫技用高级并发工具
真实项目里更可能用线程池 + Future 链式提交
如果任务有返回值、需错误传播、或要统一管理生命周期,ExecutorService 配合 Future 更健壮:
立即学习“Java免费学习笔记(深入)”;
ExecutorService es = Executors.newFixedThreadPool(1); // 单线程池天然串行 Future> f1 = es.submit(() -> doT1()); f1.get(); // 阻塞直到完成 Future> f2 = es.submit(() -> doT2()); f2.get();
注意:单线程池虽能保证顺序,但和 join() 语义不同——它不控制线程实例,只控制任务提交顺序;若任务内部又启新线程,仍可能并发。
join()、get()、await())都必须响应中断——漏写 Thread.interrupted() 或未恢复中断状态,会导致上层无法优雅关闭。










