Java 并发核心笔记:自定义 MyAtomicInteger 与 CAS 机制详解

一、核心目标

本文围绕自定义 MyAtomicInteger 类展开,拆解其底层实现逻辑,掌握 CAS 无锁编程思想volatile 可见性保障、Unsafe 类的核心作用,理解 Java 原子类(如 AtomicInteger)的底层原理,解决多线程下变量自增的线程安全问题。

二、自定义 MyAtomicInteger 完整代码(无修改)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import sun.misc.Unsafe;
import java.lang.reflect.Field;

public class MyAtomicInteger {
// 核心依赖:Unsafe类(直接操作内存、执行CAS原子指令)
private static final Unsafe unsafe = getUnsafe();

// value变量在对象中的内存偏移地址(Unsafe操作内存需要此地址)
private static final long valueOffset;

// 核心变量:用volatile修饰,保证多线程可见性、禁止指令重排
private volatile int value;

// 静态代码块:初始化valueOffset(类加载时执行,仅一次)
static {
try {
// 1. 通过反射获取value字段
Field valueField = MyAtomicInteger.class.getDeclaredField("value");
// 2. 获取value字段在MyAtomicInteger对象中的内存偏移地址
valueOffset = unsafe.objectFieldOffset(valueField);
} catch (Exception e) {
throw new Error(e);
}
}

// 线程安全的自增操作(返回自增后的值)
public int incrementAndGet() {
// 自旋锁:CAS失败则不断重试,直到成功(无锁核心)
for (;;) {
int current = get(); // 获取当前value最新值(volatile保证可见性)
int next = current + 1; // 计算自增后的新值
// 执行CAS对比交换:成功则返回新值,失败则重新自旋
if (compareAndSet(current, next)) {
return next;
}
System.out.println(Thread.currentThread().getName() + "自旋等待中。。。");
}
}

// 获取当前value值(volatile保证每次读取都是内存最新值)
public final int get() {
return value;
}

// CAS核心方法:对比并交换(原子操作)
private boolean compareAndSet(int expect, int update) {
// 调用Unsafe的原生CAS方法:this=当前对象,valueOffset=内存偏移,expect=预期值,update=新值
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

// 反射获取Unsafe实例(Unsafe默认禁止外部直接访问)
static Unsafe getUnsafe() {
try {
// 1. 获取Unsafe类的静态私有属性"theUnsafe"
Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");
// 2. 突破访问权限限制(private→可访问)
theUnsafeField.setAccessible(true);
// 3. 静态属性无需传入对象,传null即可获取实例
return (Unsafe) theUnsafeField.get(null);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

三、核心组件拆解(每部分的作用与意义)

1. Unsafe 类:Java 底层 “魔法工具类”

  • 定位:Java 提供的底层操作工具类,直接操作内存、执行 CPU 原子指令,是实现无锁编程的核心依赖。
  • 特点
    • 构造方法私有,仅允许 JDK 内部类访问,外部需通过反射获取实例;
    • 功能强大但风险高(直接操作内存可能导致程序崩溃),官方不推荐普通开发直接使用。
  • 核心用法(本类中)
    • objectFieldOffset(Field field):获取字段在对象中的内存偏移地址;
    • compareAndSwapInt(Object obj, long offset, int expect, int update):执行 CAS 原子操作,对比 obj 对象 offset 地址的值是否为 expect,是则更新为 update,返回 true;否则返回 false。

2. valueOffset:内存偏移地址

  • 作用value 变量在 MyAtomicInteger 对象中的内存地址偏移量。
  • 为什么需要:Unsafe 操作内存时,需要通过 “对象实例 + 内存偏移” 精准定位到 value 变量的内存位置,才能直接读写该变量的值。
  • 初始化时机:静态代码块中初始化(类加载时执行一次),避免每次 CAS 操作都计算偏移地址,提升效率。

3. volatile 修饰的 value 变量

  • 核心作用:保证 value 的 可见性 和 禁止指令重排
    • 可见性:一个线程修改 value 后,其他线程能立刻读取到内存中的最新值(避免读取到本地缓存的旧值);
    • 禁止指令重排:确保 value 的读写操作按代码顺序执行,不会被 JVM 优化打乱。
  • 注意:volatile 仅保证可见性,不保证原子性(如 value++ 仍非线程安全),需配合 CAS 才能实现原子更新。

4. 自旋锁 + CAS:无锁编程的核心逻辑

(1)自旋锁:for(;;) 死循环

  • 作用:CAS 操作失败时,线程不阻塞(避免锁竞争的上下文切换开销),而是重新读取最新值重试,直到成功。
  • 优势:低开销(无阻塞 / 唤醒的上下文切换),高并发场景下吞吐量比 synchronized 锁更高。
  • 缺点:竞争激烈时,失败线程会不断自旋,消耗 CPU 资源(可通过限制重试次数优化)。

(2)CAS 机制:Compare and Swap(比较并交换)

  • 定义:一种 CPU 原子指令支持的无锁同步机制,核心是 “先对比,再更新”,保证操作的原子性(不可打断)。
  • 核心参数(3 个):
    • 内存地址 V(valueOffset 对应的内存位置);
    • 预期值 A(线程读取到的 value 快照,如 current);
    • 新值 B(线程想要更新的值,如 next)。
  • 执行逻辑(原子性):
    1. 对比内存地址 V 中的实际值与预期值 A;
    2. 若相等:说明变量未被其他线程修改,将内存值更新为 B,返回 true;
    3. 若不相等:说明变量已被修改,放弃更新,返回 false。

5. 反射获取 Unsafe:突破访问限制

  • 原因:Unsafe 类的 theUnsafe 属性是静态私有(private static final Unsafe theUnsafe),外部无法直接访问。
  • 步骤
    1. 通过 Class.getDeclaredField() 获取该属性;
    2. 调用 setAccessible(true) 突破私有访问限制;
    3. 静态属性通过 field.get(null) 获取实例(无需传入对象)。

四、核心流程:多线程下 incrementAndGet () 如何保证线程安全?

以两个线程同时调用 incrementAndGet() 为例,流程如下:

  1. 线程 1、线程 2 同时调用 get(),通过 volatile 可见性获取到 value = 0(当前最新值);
  2. 两者均计算新值 next = 1
  3. 线程 1 先执行 compareAndSet(0, 1)
    • Unsafe 定位到 value 的内存地址,对比实际值(0)与预期值(0)相等;
    • 原子更新内存值为 1,返回 true,线程 1 直接返回 1;
  4. 线程 2 执行 compareAndSet(0, 1)
    • 对比内存实际值(1)与预期值(0)不相等,返回 false;
    • 线程 2 进入自旋,重新调用 get() 获取最新值(1),计算新值(2);
    • 再次执行 CAS 操作,对比 1 与内存值(1)相等,更新为 2,返回 true 并返回 2;
  5. 最终 value 正确自增,无线程安全问题(不会出现值覆盖)。

五、关键知识点:为什么这样设计能保证线程安全?

1. 原子性保障:CAS 指令 + 硬件支持

CAS 操作由 CPU 底层原子指令(如 x86 的 cmpxchg)实现:

  • 单核 CPU:一条指令完成 “对比 + 交换”,天然原子性;
  • 多核 CPU:通过总线锁定(Lock 前缀指令)阻止其他核心同时操作该内存地址,确保原子性。

2. 可见性保障:volatile 修饰

  • 线程修改 value 后,会立刻将值刷新到主内存;
  • 其他线程读取 value 时,会跳过本地缓存,直接从主内存读取最新值。

3. 无锁设计:避免锁竞争开销

  • 相比 synchronized 等互斥锁,CAS 无需创建锁、释放锁,也避免了线程阻塞 / 唤醒的上下文切换(重量级操作);
  • 竞争失败的线程仅自旋重试,开销远低于锁竞争。

六、使用场景与实战示例

1. 适用场景

  • 多线程下的计数器、累加器(如接口访问次数统计、任务执行进度计数);
  • 低 - 中并发场景的变量原子更新(高并发下自旋消耗 CPU,可改用 ReentrantLock)。

2. 实战验证(线程安全测试)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class MyAtomicIntegerTest {
public static void main(String[] args) throws InterruptedException {
MyAtomicInteger count = new MyAtomicInteger();
int threadNum = 3; // 3个线程
Thread[] threads = new Thread[threadNum];

// 每个线程执行1000次自增
for (int i = 0; i < threadNum; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++) {
count.incrementAndGet();
}
}, "线程" + (i + 1));
threads[i].start();
}

// 等待所有线程执行完毕
for (Thread t : threads) {
t.join();
}

System.out.println("最终计数:" + count.get()); // 必然输出 3000(无线程安全问题)
}
}

七、常见问题与注意事项

1. CAS 的 ABA 问题

  • 定义:线程 1 读取值为 A,准备更新为 B;线程 2 先将 A 改为 C,再改回 A;线程 1 执行 CAS 时,发现值仍为 A,误以为未被修改,成功更新,导致潜在逻辑错误。
  • 解决方案:给变量加版本号 / 时间戳(如 Java 官方的 AtomicStampedReference 类),CAS 同时对比 “值 + 版本号”。

2. 自旋消耗 CPU

  • 问题:高并发下,大量线程竞争同一个变量,会导致失败线程不断自旋,占用 CPU 资源(可能导致 CPU 使用率飙升)。
  • 优化方向:限制自旋次数(如重试 3 次后改用锁)、使用自适应自旋(根据历史重试情况动态调整次数)。

3. 只能原子更新单个变量

  • 问题:CAS 仅能原子更新一个变量,无法直接原子更新多个变量。
  • 解决方案:将多个变量封装为对象(如用 AtomicReference 包装自定义对象),通过更新对象的原子性间接实现多变量原子更新。

八、学习建议:是否需要掌握?(分场景)

1. 初学者 / 初级开发

  • 目标:无需死记代码实现,重点理解核心思想:
    • 知道 MyAtomicInteger 是解决 “多线程自增线程安全” 的;
    • 记住关键技术:CAS、volatile、Unsafe(知道它们各自的作用);
    • 会使用官方 AtomicInteger(调用 incrementAndGet() 等方法)即可。

2. 中级开发 / 准备面试

  • 目标:能讲清完整原理,最好能手写简化版:
    • 能解释 “为什么加 volatile”“valueOffset 的作用”“自旋的目的”;
    • 能讲清 CAS 机制的原子性保障;
    • 这是 Java 并发的高频面试题,掌握后能体现底层思维。

3. 高级开发 / 底层框架开发

  • 目标:掌握细节与扩展:
    • 理解 Unsafe 的其他用法(如内存分配、线程挂起 / 唤醒);
    • 能解决 CAS 的 ABA 问题、自旋优化等进阶场景;
    • 可基于 Unsafe 扩展自定义并发工具(如分布式锁、原子类)。

九、核心总结

  1. 自定义 MyAtomicInteger 是 CAS 机制的经典实践,完美复刻了官方 AtomicInteger 的核心原理;
  2. 核心设计:Unsafe 实现底层原子操作 + volatile 保证可见性 + 自旋锁 + CAS 对比交换;
  3. 优势:无锁、高效、线程安全,避免了锁竞争的上下文切换开销;
  4. 学习重点:理解 “原理” 比死记代码更重要,掌握 CAS、volatile、Unsafe 的核心作用,能应对日常并发场景和面试。