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

AI-摘要
LNotes-AI GPT
AI初始化中...
介绍自己 🙈
生成本文简介 👋
推荐相关文章 📖
前往主页 🏠
前往爱发电购买
Java 并发核心笔记:自定义 MyAtomicInteger 与 CAS 机制详解
Liuxz一、核心目标
本文围绕自定义 MyAtomicInteger 类展开,拆解其底层实现逻辑,掌握 CAS 无锁编程思想、volatile 可见性保障、Unsafe 类的核心作用,理解 Java 原子类(如 AtomicInteger)的底层原理,解决多线程下变量自增的线程安全问题。
二、自定义 MyAtomicInteger 完整代码(无修改)
1 | import sun.misc.Unsafe; |
三、核心组件拆解(每部分的作用与意义)
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)。
- 内存地址 V(
- 执行逻辑(原子性):
- 对比内存地址 V 中的实际值与预期值 A;
- 若相等:说明变量未被其他线程修改,将内存值更新为 B,返回 true;
- 若不相等:说明变量已被修改,放弃更新,返回 false。
5. 反射获取 Unsafe:突破访问限制
- 原因:Unsafe 类的
theUnsafe属性是静态私有(private static final Unsafe theUnsafe),外部无法直接访问。 - 步骤:
- 通过
Class.getDeclaredField()获取该属性; - 调用
setAccessible(true)突破私有访问限制; - 静态属性通过
field.get(null)获取实例(无需传入对象)。
- 通过
四、核心流程:多线程下 incrementAndGet () 如何保证线程安全?
以两个线程同时调用 incrementAndGet() 为例,流程如下:
- 线程 1、线程 2 同时调用
get(),通过 volatile 可见性获取到value = 0(当前最新值); - 两者均计算新值
next = 1; - 线程 1 先执行
compareAndSet(0, 1):- Unsafe 定位到
value的内存地址,对比实际值(0)与预期值(0)相等; - 原子更新内存值为 1,返回 true,线程 1 直接返回 1;
- Unsafe 定位到
- 线程 2 执行
compareAndSet(0, 1):- 对比内存实际值(1)与预期值(0)不相等,返回 false;
- 线程 2 进入自旋,重新调用
get()获取最新值(1),计算新值(2); - 再次执行 CAS 操作,对比 1 与内存值(1)相等,更新为 2,返回 true 并返回 2;
- 最终
value正确自增,无线程安全问题(不会出现值覆盖)。
五、关键知识点:为什么这样设计能保证线程安全?
1. 原子性保障:CAS 指令 + 硬件支持
CAS 操作由 CPU 底层原子指令(如 x86 的 cmpxchg)实现:
- 单核 CPU:一条指令完成 “对比 + 交换”,天然原子性;
- 多核 CPU:通过总线锁定(Lock 前缀指令)阻止其他核心同时操作该内存地址,确保原子性。
2. 可见性保障:volatile 修饰
- 线程修改
value后,会立刻将值刷新到主内存; - 其他线程读取
value时,会跳过本地缓存,直接从主内存读取最新值。
3. 无锁设计:避免锁竞争开销
- 相比
synchronized等互斥锁,CAS 无需创建锁、释放锁,也避免了线程阻塞 / 唤醒的上下文切换(重量级操作); - 竞争失败的线程仅自旋重试,开销远低于锁竞争。
六、使用场景与实战示例
1. 适用场景
- 多线程下的计数器、累加器(如接口访问次数统计、任务执行进度计数);
- 低 - 中并发场景的变量原子更新(高并发下自旋消耗 CPU,可改用
ReentrantLock)。
2. 实战验证(线程安全测试)
1 | public class MyAtomicIntegerTest { |
七、常见问题与注意事项
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 扩展自定义并发工具(如分布式锁、原子类)。
九、核心总结
- 自定义
MyAtomicInteger是 CAS 机制的经典实践,完美复刻了官方AtomicInteger的核心原理; - 核心设计:
Unsafe实现底层原子操作 +volatile保证可见性 + 自旋锁 + CAS 对比交换; - 优势:无锁、高效、线程安全,避免了锁竞争的上下文切换开销;
- 学习重点:理解 “原理” 比死记代码更重要,掌握 CAS、volatile、Unsafe 的核心作用,能应对日常并发场景和面试。
评论
匿名评论隐私政策
✅ 你无需删除空行,直接评论以获取最佳展示效果




