Java多线程同步方法未输出预期顺序问题咨询
问题分析与解答
核心原因:synchronized管线程安全,不管执行顺序
你代码里的synchronized方法确实起到了作用——它保证了同一时间只有一个线程能执行call()方法,所以不会出现输出交叉(比如[Hello[Synchronized]World]这种错误结果)。但它完全不负责控制线程的执行顺序。
当你调用thread.start()后,线程会进入「就绪状态」,什么时候被CPU调度执行、什么时候能抢到共享对象的锁,完全由JVM的线程调度器决定,和你调用start()的顺序没有必然联系。你看到的[Hello] [World] [Synchronized]是完全合法的输出,书中给出的[Hello] [Synchronized] [World]只是众多合法输出中的一种,不是唯一的标准答案。
你尝试的方案为什么没用
- 调整
start()和join()的顺序:start()只是启动线程,不决定执行顺序;join()只是让主线程等待子线程完成,同样不影响子线程之间的调度顺序。 - 继承
Thread而非实现Runnable:两种创建线程的方式本质上没有区别,最终都是由JVM调度器管理,解决不了顺序问题。
如何实现严格的执行顺序(如果需要)
如果业务场景必须保证线程按Hello→Synchronized→World的顺序执行,你需要额外的同步逻辑来控制,比如:
方式1:手动控制线程执行顺序(串行化,失去多线程并发意义)
直接在主线程中顺序调用call()方法,不需要启动线程:
public static void main(String[] args) { Callme target = new Callme(); target.call("Hello"); target.call("Synchronized"); target.call("World"); }
方式2:用信号量/屏障控制线程顺序
比如使用CountDownLatch让每个线程等待前一个线程完成:
class Callme { void call(String msg) { // 这里不需要synchronized,因为顺序执行不会有并发问题 System.out.print("[" + msg); try { Thread.sleep(1000); } catch (InterruptedException e) { System.out.println("Interrupted"); } System.out.println("]"); } } class Caller implements Runnable { String msg; Callme target; CountDownLatch latch; public Caller(Callme targ, String s, CountDownLatch latch) { target = targ; msg = s; this.latch = latch; } public void run() { try { latch.await(); // 等待前一个线程完成 target.call(msg); } catch (InterruptedException e) { e.printStackTrace(); } } } class Synch { public static void main(String[] args) throws InterruptedException { Callme target = new Callme(); CountDownLatch latch1 = new CountDownLatch(0); CountDownLatch latch2 = new CountDownLatch(1); CountDownLatch latch3 = new CountDownLatch(1); Caller hello = new Caller(target, "Hello", latch1); Caller synch = new Caller(target, "Synchronized", latch2); Caller world = new Caller(target, "World", latch3); new Thread(hello).start(); Thread.sleep(100); // 确保hello先执行 latch2.countDown(); // 允许synch执行 new Thread(synch).start(); Thread.sleep(1100); // 等待synch执行完 latch3.countDown(); // 允许world执行 new Thread(world).start(); } }
这种方式虽然能保证顺序,但本质上是把并发变成了串行,只有在确实需要严格顺序的场景下才建议使用。
内容的提问来源于stack exchange,提问作者Alexander Yermolenko
相关产品推荐
相关产品推荐

