You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何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的不重复序列。

但问题出在线程的执行顺序和打印时机上:每个线程的任务包含两个独立步骤:

  1. 调用nm.incrementCount()获取递增后的数值
  2. 调用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 15:55:17