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

为何volatile关键字无法解决共享变量递增问题?

嘿,这个问题问得特别典型!好多刚接触Java并发的小伙伴都会在volatile这里踩坑,我来给你掰扯清楚~

首先先把你没写完的代码补全(方便大家理解场景):

public class Main {
    private volatile int count;

    public static void main(String[] args) {
        Main main = new Main();
        main.doJob();
    }

    private void doJob() {
        Runnable runnable1 = () -> {
            for (int i = 0; i < 10000; i++) {
                incrementCount();
            }
        };
        Runnable runnable2 = () -> {
            for (int i = 0; i < 10000; i++) {
                incrementCount();
            }
        };
        Thread t1 = new Thread(runnable1);
        Thread t2 = new Thread(runnable2);
        t1.start();
        t2.start();
        // 等待两个线程执行完再输出结果
        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("最终count值:" + count);
    }

    private void incrementCount() {
        count++;
    }
}

先搞懂volatile到底能干嘛,不能干嘛

volatile关键字的核心能力只有两个:

  • 保证可见性:一个线程修改了volatile变量后,其他线程能立刻读到主内存里的最新值,不会拿着自己工作内存里的旧缓存不放。
  • 禁止指令重排序:JVM不会对volatile相关的代码乱排执行顺序,保证代码按你写的顺序跑。

但!它完全不保证操作的原子性——这就是你代码出问题的根源!

为什么count++会翻车?

count++看着是一行代码,其实在底层是拆成三步的:

  1. 从主内存把count的当前值读到线程自己的工作内存
  2. 在工作内存里给这个值加1
  3. 把加完后的新值写回主内存

虽然volatile能保证每一步的可见性,但这三步是分开执行的,中间可能被其他线程插进来!举个例子:

  • 线程A读到count=5,还没来得及加1
  • 线程B也读到了count=5
  • 线程A完成加1,把6写回主内存(volatile让线程B能立刻看到这个变化,但没用啊,线程B已经拿到旧值5了)
  • 线程B接着把自己手里的5加1,也写回6

这就导致了一次递增操作被“吃掉”了,最终结果肯定比预期的20000小。

怎么解决这个问题?

给你两个靠谱的方案:

方案1:用synchronized保证原子性

给递增方法加个锁,让同一时间只有一个线程能执行这个操作,把三步变成一个不可拆分的整体:

// 给方法加锁
private synchronized void incrementCount() {
    count++;
}

或者用代码块锁(效果一样,更灵活):

private void incrementCount() {
    synchronized (this) {
        count++;
    }
}

方案2:用原子类AtomicInteger

Java的java.util.concurrent.atomic包专门提供了原子操作类,它们的方法都是底层保证原子性的,不用手动加锁:

import java.util.concurrent.atomic.AtomicInteger;

public class Main {
    // 把int换成AtomicInteger,初始值设为0
    private AtomicInteger count = new AtomicInteger(0);

    // ...其他代码不变...

    private void incrementCount() {
        // 调用原子递增方法
        count.incrementAndGet();
    }
}

用这两种方法,最终结果就能稳定得到20000啦~

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 08:03:08