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

多线程更新AtomicInteger未达预期值,求代码问题排查

问题排查与修复方案

你的问题核心出在Counter类的单例实现不是线程安全的,导致多线程环境下可能创建多个Counter实例,每个实例都持有自己独立的Holder对象(以及里面的AtomicInteger n)。当10个线程同时调用Counter.getInstance()时,会有概率创建多个Counter实例,部分线程操作的是不同的AtomicInteger变量,最终累加的结果自然就不是预期的1000000了。

为什么会出现多个Counter实例?

看你的getInstance()方法:

public static Counter getInstance() {
    if (counter == null) {
        counter = new Counter();
    }
    return counter;
}

这是典型的懒汉式单例,但完全没做线程安全保护。当多个线程同时进入if (counter == null)判断时,都会认为counter还没初始化,进而各自创建新的Counter实例。比如线程A和线程B同时检查到counter为null,都会执行counter = new Counter();,这就导致内存中存在多个Counter对象,每个对象的holder.n都是独立的AtomicInteger。

举个例子:如果有3个线程各自创建了一个Counter实例,那么最终的结果就是这3个AtomicInteger的累加值,总和肯定小于1000000。

修复方案

我们需要把Counter的单例实现改成线程安全的,这里提供几种常见且可靠的方式:

方案1:给getInstance方法加同步锁

最直接的方式是给getInstance()方法加上synchronized关键字,确保同一时间只有一个线程能进入方法执行初始化:

public class Counter {
    private static Counter counter;
    Holder holder = new Holder();

    public static synchronized Counter getInstance() {
        if (counter == null) {
            counter = new Counter();
        }
        return counter;
    }

    class Holder {
        AtomicInteger n = new AtomicInteger(0);
    }
}

方案2:双重检查锁定(DCL)+ volatile

如果担心synchronized方法的性能开销(虽然这个场景下影响极小),可以用双重检查锁定,同时给counter变量加上volatile修饰,禁止指令重排:

public class Counter {
    private static volatile Counter counter; // 加volatile禁止指令重排
    Holder holder = new Holder();

    public static Counter getInstance() {
        if (counter == null) {
            synchronized (Counter.class) {
                if (counter == null) { // 二次检查,避免后续线程重复初始化
                    counter = new Counter();
                }
            }
        }
        return counter;
    }

    class Holder {
        AtomicInteger n = new AtomicInteger(0);
    }
}

方案3:使用枚举实现单例(推荐)

枚举是Java中最安全的单例实现方式,天然线程安全,还能防止反序列化破坏单例:

enum Counter {
    INSTANCE;

    private Holder holder = new Holder();

    public Holder getHolder() {
        return holder;
    }

    class Holder {
        AtomicInteger n = new AtomicInteger(0);
    }
}

对应的调用代码要改成Counter.INSTANCE.getHolder().n.incrementAndGet();

另外,你原来用Thread.sleep(10000)等待线程执行完成的方式不太可靠——如果线程实际执行时间超过10秒,输出结果还是不对。更好的方式是调用thread.join()等待所有线程执行完毕:

for (Thread thread : list) {
    thread.start();
}
// 替换sleep为join,确保所有线程执行完成再输出
for (Thread thread : list) {
    try {
        thread.join();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}
System.out.println(Counter.getInstance().holder.n);

验证修复效果

修复单例的线程安全问题后,所有线程都会操作同一个AtomicInteger变量,incrementAndGet()是原子操作,最终结果就会稳定为1000000了。

内容的提问来源于stack exchange,提问作者zonyang

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 09:01:07