Java多线程使用Thread.join()为何未按预期控制执行顺序?
核心问题:对
Thread.join()的作用存在认知偏差 第一问:当前代码输出混乱,不代表Thread.join()没有实际价值
你观察到的打印内容交叉,本质上是因为Thread.join()的设计目标和你预期的使用场景完全不匹配——它从一开始就不是用来约束多个子线程之间的执行顺序的。
关于Thread.join()的准确定义:
当一个线程调用
threadA.join()时,当前正在执行调用动作的线程会进入阻塞等待状态,直到目标线程threadA完全执行结束,当前线程才会恢复继续往下执行。
结合你写的代码拆解实际执行流程:
- 主线程先后执行
t1.start()、t2.start(),两个线程会立刻进入操作系统的可调度队列,谁先抢到CPU时间片、抢到多少时间片,完全由操作系统调度逻辑决定,此时两个线程之间没有任何先后约束,自然会出现打印内容交叉的情况。 - 你写的
t1.join()、t2.join(),本质上只约束主线程和两个子线程的关系:主线程会等t1完全跑完,再等t2完全跑完,最后才会打印Finished。可以做个对比实验:把这两个join注释掉,会经常出现Finished被打印在Hello和World内容前面的情况,这就是join实际发挥的作用——它保证了主线程不会抢在两个子线程前面结束执行。
也就是说,你当前代码里的join,根本没有给t1和t2之间加任何先后约束,只是让主线程等他俩而已,两个子线程自由调度出现交叉输出是完全正常的表现,不是join失效了。
第二问:如何调整代码才能保证固定输出顺序
要实现“先打印Hello、再打印World、最后打印Finished”的固定顺序,核心是给两个子线程之间加上明确的等待约束,不要让两个线程同时进入无限制的自由调度状态,两种常见的有效调整方式:
方式1:在主线程中串行控制线程启动时机
把原来“两个线程同时启动,再一起等待”的逻辑,改成“启动一个、等它跑完、再启动下一个”:
Thread t1 = new Thread(hello); Thread t2 = new Thread(world); try { // 启动第一个线程 t1.start(); // 主线程阻塞,等待t1完全执行结束 t1.join(); // t1跑完后,再启动第二个线程 t2.start(); // 主线程阻塞,等待t2完全执行结束 t2.join(); System.out.println("Finished"); } catch (InterruptedException ie) { System.out.println("Interruption"); }
这种写法下,t2根本不会进入可调度队列,直到t1完全结束,自然不会出现打印内容交叉。
方式2:让后启动的线程主动等待前序线程执行完成
如果需要两个线程同时触发启动流程,就给t2的执行逻辑加上“等t1跑完再执行自身任务”的约束:
Thread t1 = new Thread(hello); Thread t2 = new Thread(() -> { try { // t2一启动,先阻塞等待t1执行完成 t1.join(); } catch (InterruptedException e) { e.printStackTrace(); } // t1跑完后,再执行自身的打印逻辑 System.out.print("W"); System.out.print("o"); System.out.print("r"); System.out.print("l"); System.out.print("d\n"); }); try { t1.start(); t2.start(); t2.join(); System.out.println("Finished"); } catch (InterruptedException ie) { System.out.println("Interruption"); }
这种写法哪怕两个线程同时进入调度队列,t2启动后第一步就会卡在等待状态,直到t1跑完才会开始打印World,不会出现内容交叉。
内容的提问来源于stack exchange,提问作者Yan Zhuang
相关产品推荐
相关产品推荐

