Java多线程为何未按启动顺序执行结束?如何实现预期有序执行?
问题成因
- 操作系统的线程调度无默认顺序约束:调用
Thread.start()仅表示将线程提交到操作系统就绪队列,CPU分配时间片的顺序完全由内核调度策略决定,和start()的调用顺序没有绑定关系,所以线程的实际执行顺序天然是随机的。 join()的行为符合预期:join()的作用是让调用方(此处为主线程)阻塞,直到目标线程运行结束才返回。你是按线程数组的存储顺序依次调用join(),所以即使后面的线程先运行结束,主线程也会先等待排在前面的线程终止,因此join日志会严格按数组顺序打印,且必然在对应线程结束后才输出。- 同步范围仅覆盖减血操作:你给
hurt()方法加了synchronized,仅保证hp -= 1的原子性,减血操作完成后锁就会释放,后续的打印HP、Thread.sleep()、打印结束日志的步骤都没有同步约束,多个线程的这些步骤会交叉执行,自然会出现日志顺序和减血顺序不匹配的问题。
有序执行实现方案
如果需要让10个线程严格按启动顺序执行减血、打印、结束的完整逻辑,可以通过序号准入控制实现,以下是可直接运行的修改方案:
首先新增一个准入控制的volatile变量,每个线程持有自己的序号,只有轮到自己的序号时才执行完整逻辑,执行完更新准入序号:
public class TestHero { // 记录当前允许执行的线程序号,从0开始 private static volatile int currentAllowed = 0; // 用于控制准入序号更新的锁,避免并发更新冲突 private static final Object lock = new Object(); public static void main(String[] args) { Hero gareen = new Hero("Gareen", 100, 10); System.out.printf("Initial HP of Gareen: %f\n",gareen.hp); int N = 10; Thread[] hurtThreads = new Thread[N]; for (int i = 0; i < N; i++) { // 把当前序号存为final变量,供lambda使用 final int threadIndex = i; Thread t1 = new Thread(() -> { // 自旋等待到自己的序号 while (currentAllowed != threadIndex) { // 空转等待,也可以加Thread.yield()降低CPU占用 } // 执行完整逻辑 gareen.hurt(); System.out.println(Thread.currentThread().getName() + ": Gareen lost 1 HP,now his HP: " + gareen.hp); try{ Thread.sleep(3000); } catch (InterruptedException e){ e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " ended"); // 更新准入序号,放行下一个线程 synchronized (lock) { currentAllowed++; } }); t1.start(); System.out.println(t1.getName() + " started"); hurtThreads[i] = t1; } for(Thread t : hurtThreads){ try { t.join(); System.out.println(t.getName() + " joined "); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("Now, Gareen's HP: " + gareen.hp); } }
修改后运行就会完全符合你的预期:线程按启动顺序依次执行减血、打印HP、休眠、打印结束,最终HP为90,join日志也会按顺序输出。如果不需要多线程并发只是要顺序执行任务,直接在主线程循环调用hurt()即可,上述方案仅用于多线程顺序执行的练习场景。
内容的提问来源于stack exchange,提问作者Tracker647
相关产品推荐
相关产品推荐

