原子数组
大约 2 分钟
用来保护数组内部数据的线程安全
原子数组分类
- AtomicIntegerArray
- AtomicLongArray
- AtomicReferenceArray
这是一个常见的误解。
原子数组(如 AtomicIntegerArray)的实现原理 完全不依赖 AQS。
它的底层实现非常“硬核”,直接依赖于 Unsafe 类 和 CPU 指令。
1. 为什么不是 AQS?
- AQS (AbstractQueuedSynchronizer) 是用来构建锁(Lock)、信号量(Semaphore)等同步器的框架。它的核心是维护一个线程等待队列(CLH 队列),解决的是“当资源被占用了,线程该去哪排队睡觉”的问题。
- AtomicIntegerArray 是为了实现非阻塞(Non-blocking)的原子操作。它的目标是:我想改这个值,如果改失败了我就重试(自旋),而不是去睡觉。所以它不需要队列,也不需要 AQS。
2. 原子数组的真正实现原理
它主要由三根支柱支撑:
支柱一:Unsafe 直接内存操作 (指针偏移量)
普通的 Java 数组访问 arr[i] 会有越界检查。AtomicIntegerArray 则直接把数组当成一块连续的内存。 它在初始化时,会计算出两个关键参数:
base: 数组在内存中的起始地址偏移量。scale: 每个元素占多少字节(int 是 4 字节)。
只要知道下标 i,它就能算出第 i 个元素的精确内存地址: $$地址 = base + i \times scale$$
支柱二:volatile 语义的读写
虽然数组里的元素不能被声明为 volatile(Java 语法不支持 volatile int[] arr 让元素 volatile),但在读写时,它使用了 Unsafe 的特殊方法:
- 读:
Unsafe.getIntVolatile(obj, offset)—— 强制从主内存读,保证可见性。 - 写:
Unsafe.putIntVolatile(obj, offset, value)—— 强制刷回主内存,保证可见性。
支柱三:CAS (Compare-And-Swap)
这是并发修改的核心。当你要把 arr[i] 从 A 改成 B 时: 调用 Unsafe.compareAndSwapInt(obj, offset, expect, update)。
这对应到底层 CPU 指令(如 x86 的 LOCK CMPXCHG): “锁定这块内存地址,看它是不是 A?是就改成 B,不是就告诉我失败。” 这个过程不需要线程挂起,不需要上下文切换,速度极快。
总结
- Lock / Semaphore / CountDownLatch -> 基于 AQS (排队、阻塞、唤醒)。
- AtomicInteger / AtomicIntegerArray -> 基于 CAS + Unsafe (自旋、无锁、直接内存操作)。
它们是 Java 并发包中两套完全不同的底层机制。