volatile 关键字不能保证复合操作(如自增操作 i++)的线程安全,因为复合操作不是单一的,而是由多个独立的步骤组成的,这些步骤可能会交错执行。自增操作可以分解为以下三个基本步骤:

  1. 读取:将变量 i 的当前值读入寄存器或其他临时内存位置。

  2. 修改:在寄存器中将临时值增加1。

  3. 写入:将新值写回主内存中的变量 i。

假设我们有一个声明为 volatile 的整数 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。

补充说明:一般腾讯一面都是特别基础的问题,所以我们必须加强基础知识的学习。

本篇文章来源于微信公众号: 互联网面试小帮手



微信扫描下方的二维码阅读本文

此作者没有提供个人介绍
最后更新于 2024-04-28