为何Java多线程程序输出无序?使用AtomicInteger仍出现顺序问题
为什么使用AtomicInteger后多线程输出的计数顺序仍然混乱?
问题描述
我编写了以下Java多线程代码,使用AtomicInteger保证计数递增的原子性,预期输出的计数会按1、2、3...的顺序打印,但实际输出中出现了数值顺序混乱的情况(比如4先于3出现):
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicInteger; public class ScheduledExecuterServicePractice { public static void main(String[] args) { System.out.println("Started"); NumberManager nm= new NumberManager(); ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(10); for(int i=0;i<10;i++) { newFixedThreadPool.submit(() -> System.out.println(Thread.currentThread().toString()+" "+nm.incrementCount())); } newFixedThreadPool.shutdown(); } } class NumberManager{ AtomicInteger count = new AtomicInteger(); public int incrementCount() { return count.incrementAndGet(); } }
执行输出:
Started Thread[pool-2-thread-3,5,main] 1 Thread[pool-2-thread-4,5,main] 4 Thread[pool-2-thread-1,5,main] 3 Thread[pool-2-thread-5,5,main] 5 Thread[pool-2-thread-2,5,main] 2 Thread[pool-2-thread-6,5,main] 6 Thread[pool-2-thread-7,5,main] 8 Thread[pool-2-thread-8,5,main] 7 Thread[pool-2-thread-9,5,main] 9 Thread[pool-2-thread-10,5,main] 10
原因分析
AtomicInteger.incrementAndGet()确实保证了递增操作本身的原子性——不会出现多个线程同时修改计数导致的重复、丢失或错误值,最终所有线程拿到的计数一定是1到10的不重复序列。
但问题出在线程的执行顺序和打印时机上:每个线程的任务包含两个独立步骤:
- 调用
nm.incrementCount()获取递增后的数值 - 调用
System.out.println()打印线程信息和数值
AtomicInteger只保证第一步是原子的,但这两个步骤并没有被绑定成一个原子操作。线程调度由JVM和操作系统决定,某个线程完成递增后,可能因为调度切换,让其他线程先完成自己的递增和打印操作。
举个实际场景:
- 线程3先完成递增得到1,但还没开始打印,此时线程4被调度执行,完成递增得到4并立刻打印
- 之后线程1才被调度完成递增得到3并打印
这就导致了4出现在3前面的情况。
可选解决方案
如果需要保证输出的顺序和计数一致,可以通过以下方式实现:
方案1:同步整个任务流程
给"递增+打印"的操作加同步锁,让每个线程必须等前一个线程完成整个操作后才能执行:
newFixedThreadPool.submit(() -> { synchronized(nm) { System.out.println(Thread.currentThread().toString()+" "+nm.incrementCount()); } });
这种方式能保证输出顺序,但会让多线程任务变成串行执行,损失并行效率。
方案2:分离计算与输出
让线程只负责生成计数,将结果存入线程安全的队列,主线程在所有任务完成后按顺序输出:
import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; public class ScheduledExecuterServicePractice { public static void main(String[] args) throws InterruptedException { System.out.println("Started"); NumberManager nm= new NumberManager(); List<Integer> results = Collections.synchronizedList(new ArrayList<>()); ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(10); for(int i=0;i<10;i++) { newFixedThreadPool.submit(() -> results.add(nm.incrementCount())); } newFixedThreadPool.shutdown(); // 等待所有任务完成 newFixedThreadPool.awaitTermination(1, TimeUnit.MINUTES); // 主线程按顺序输出 for(int idx = 0; idx < results.size(); idx++){ System.out.println(String.format("Thread[pool-%d-thread-%d,5,main] %d", 2, idx+1, results.get(idx))); } } } class NumberManager{ AtomicInteger count = new AtomicInteger(); public int incrementCount() { return count.incrementAndGet(); } }
这种方式保留了多线程并行递增的效率,同时保证输出顺序。
内容的提问来源于stack exchange,提问作者Utkal keshari Sahu
相关产品推荐
相关产品推荐

