为何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++看着是一行代码,其实在底层是拆成三步的:
- 从主内存把count的当前值读到线程自己的工作内存
- 在工作内存里给这个值加1
- 把加完后的新值写回主内存
虽然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
相关产品推荐
相关产品推荐

