volatile 关键字不能保证复合操作(如自增操作 i++)的线程安全,因为复合操作不是单一的,而是由多个独立的步骤组成的,这些步骤可能会交错执行。自增操作可以分解为以下三个基本步骤:
-
读取:将变量 i 的当前值读入寄存器或其他临时内存位置。
-
修改:在寄存器中将临时值增加1。
-
写入:将新值写回主内存中的变量 i。
volatile int i = 0;
现在考虑以下两个线程同时运行代码 i++;。我们分解每个步骤来看发生了什么:
| 时间点 | 线程 A | 线程 B |
|---|---|---|
| T1 | 读取i的值 (0)到寄存器A | |
| T2 | 读取i的值 (0)到寄存器B | |
| T3 | 将寄存器A的值增加1 (得到1) | |
| T4 | 将寄存器B的值增加1 (得到1) | |
| T5 | 将寄存器A的值 (1)写回i | |
| T6 | 将寄存器B的值 (1)写回i |
在这个例子中,尽管 i 被声明为 volatile 并且每个读取和写入操作都是直接对主内存进行的,但没有任何东西防止两个线程同时读取相同的初始值,然后各自计算出相同的增加后的值,并最终把这个相同的值写回 i。实际上,我们期望 i 的值在两次递增后是2,但由于线程间的干扰,结果只是递增了一次,最终的值是1。
要解决这个问题,并确保复合操作的原子性,应使用 synchronized 关键字,锁机制或 java.util.concurrent.atomic 包中的原子类(例如 AtomicInteger)。下面是使用 AtomicInteger 的示例:
import java.util.concurrent.atomic.AtomicInteger;class Counter {private AtomicInteger atomicI = new AtomicInteger(0);public void increment() {atomicI.incrementAndGet();}}
在这个例子中,incrementAndGet()方法可以安全地在多线程环境中调用,无论有多少线程同时调用此方法,都能够确保每一次调用都会原子地将 atomicI 的值增加1。
补充说明:一般腾讯一面都是特别基础的问题,所以我们必须加强基础知识的学习。
本篇文章来源于微信公众号: 互联网面试小帮手
微信扫描下方的二维码阅读本文

Comments NOTHING