引言
小米最近在准备社招面试,毕竟在互联网这条“卷”路上,稍微停滞不前,就可能被更年轻的程序员们弯道超车。为了在面试中脱颖而出,小米最近疯狂刷面试题,尤其是多线程相关的知识。
这天,小米和面试官约了一场线上面试,一上来面试官就丢出了一个高频问题:
“Java 中 volatile 变量和 Atomic 变量有什么不同?”
小米心里一紧,心想:“这不就是面试八股文里常见的问题吗?” 于是,他开始了自信的作答……
volatile 变量:能见度好,但不保证原子性
1. volatile 是什么?
小米微微一笑,说道:
volatile 关键字的作用是保证变量的可见性,防止指令重排序。
通俗点说,Java 的 volatile 变量类似于“全员广播”模式。每当某个线程修改了这个变量,其他线程都能立刻看到最新的值,不会出现“脏数据”。
2. volatile 如何保证可见性?
JMM(Java 内存模型) 规定,volatile 变量在写入时,会多做一步操作:
- 普通变量赋值: 线程先从主内存中读取数据,复制到自己的工作内存,修改后再写回主内存,其他线程无法感知这个变化。
- volatile 变量赋值: 直接刷新到主内存,并让所有线程的缓存失效,从而保证所有线程都能立即看到最新的值。
就像是小米早上给群发了一条消息:“今天团建改到 3 点!” 这样,所有人都能立即收到,而不会有人还在按照原来的时间去行动。
3. 但 volatile 不能保证原子性
面试官点了点头,然后笑着问:“那 volatile 变量能保证原子性吗?”
小米立刻摇头:“不能!”
他举了个例子:
图片
看上去 count++ 只是一个简单的自增操作,但实际上它是三步操作:
- 读取 count 的值
- 执行 +1 操作
- 将新值写回 count
如果有两个线程 A 和 B 同时执行 increment() 方法:
- A 读取 count=0,执行 count+1,还没来得及写回
- B 也读取 count=0,执行 count+1,然后写回
- A 最后写回 count=1
本来执行两次 increment(),结果 count 还是 1,丢失了一次更新!这就是“并发问题”。
所以,volatile 仅仅保证了变量的可见性,并不能保证操作的原子性!
Atomic 变量:天生线程安全,保证原子性
小米接着说道:“如果想保证原子性,Java 提供了 java.util.concurrent.atomic 包,里面的 Atomic 变量是更好的选择。”
1. 什么是 Atomic 变量?
Atomic 变量 依靠 CAS(Compare-And-Swap) 机制来保证原子性。
还是刚才的例子,如果我们改用 AtomicInteger:
图片
这样,多个线程同时调用 increment() 方法时,就不会丢失数据了。
2. Atomic 如何保证原子性?
Atomic 变量的核心是 CAS(比较并交换,Compare-And-Swap),它的原理是:
- 读取旧值 oldValue
- 计算新值 newValue
- 使用 CPU 指令 尝试将 oldValue 更新为 newValue
- 如果 oldValue 还是旧值,就更新成功;如果已经被其他线程修改了,就重新尝试。
这种方式避免了传统的 锁(synchronized) 带来的性能问题,CAS 是无锁并发的基础,比 synchronized 更高效!
不过,CAS 也不是万能的,它可能导致“ABA 问题”(变量值在中间被修改了,但最终又变回去了),解决方案是 AtomicStampedReference 这种带版本号的原子变量。
volatile vs Atomic:到底该选谁?
面试官看小米讲得这么清楚,问道:“所以,在实际开发中,volatile 和 Atomic 该怎么选呢?”
小米总结道:
图片
如果只是需要保证变量的可见性,而不涉及并发修改,可以使用 volatile,比如:
- 线程间的标志位
- double check 机制的 instance 变量
如果需要保证变量的原子性,应该使用 Atomic 变量,比如:
- 计数器(AtomicInteger)
- 线程安全的累加器(LongAdder)
面试官的加分题
面试官满意地点了点头,然后问:“那 synchronized 和 Atomic 有什么不同呢?”
小米心想:“这不就是送分题吗?”
他笑着回答:
- synchronized 是一种阻塞式的同步机制,线程会进入阻塞状态,性能开销较大。
- Atomic 变量是非阻塞的,它基于 CAS 操作,不会让线程进入阻塞状态,因此更高效。
- synchronized 适合需要多个变量一起同步的场景,而 Atomic 适合单个变量的无锁操作。
面试官点头:“不错,今天的面试到这里,回去等通知吧!”
小米挂断电话,感觉这次面试发挥得不错,心里暗自高兴。为了帮助更多正在找工作的朋友,他在朋友圈里分享了今天的面试知识点:
- volatile 只保证可见性,不保证原子性
- Atomic 变量使用 CAS 机制,保证原子性,适用于计数、累加等操作
- synchronized 适用于多个变量的同步,Atomic 适用于单个变量的高效无锁操作