文章目录
-
- [1. 核心原理:CAS (Compare-And-Swap)](#1. 核心原理:CAS (Compare-And-Swap))
-
- [什么是 CAS](#什么是 CAS)
- [Java 中的 Unsafe 类](#Java 中的 Unsafe 类)
- [2. 原子类家族谱系](#2. 原子类家族谱系)
- [3. 实战演练:AtomicInteger](#3. 实战演练:AtomicInteger)
-
- 场景:多线程计数器
-
- [错误示范:普通 int](#错误示范:普通 int)
- 正确示范:AtomicInteger
- 源码剖析:getAndIncrement
- [5. 高性能利器:LongAdder (JDK 8+)](#5. 高性能利器:LongAdder (JDK 8+))
-
- 设计思想:分段锁(空间换时间)
- [AtomicLong vs LongAdder 性能对比示意](#AtomicLong vs LongAdder 性能对比示意)
在多线程并发编程的浩瀚宇宙中,线程安全 始终是核心议题。当我们需要对一个变量进行简单的累加操作时,volatile 关键字虽然能保证可见性,却无法保证原子性;而 synchronized 锁虽然强大,但在高并发场景下往往伴随着沉重的上下文切换开销。
这时,Java原子类(Atomic Classes) 应运而生。它们位于 java.util.concurrent.atomic 包下,利用底层硬件的 CAS 机制,以一种无锁(Lock-Free)的高效方式,解决了并发场景下的原子性问题。
1. 核心原理:CAS (Compare-And-Swap)
原子类的魔法源自于 CAS。它是一条 CPU 并发原语。
什么是 CAS
关于CAS的原理可以看这篇:https://blog.csdn.net/Tracycoder/article/details/156860586?spm=1011.2415.3001.10575\&sharefrom=mp_manage_link
Java 中的 Unsafe 类
Java 无法直接访问底层操作系统,而是通过 sun.misc.Unsafe 类来操作内存。原子类内部正是调用了 Unsafe 的 compareAndSwap 系列方法。
2. 原子类家族谱系
JDK 提供了丰富的原子类,主要可以分为以下四类:
| 分类 | 包含类 | 描述 |
|---|---|---|
| 基本类型 | AtomicInteger, AtomicLong, AtomicBoolean |
直接更新基本类型数据。 |
| 数组类型 | AtomicIntegerArray, AtomicLongArray, AtomicReferenceArray |
原子更新数组中的某个元素。 |
| 引用类型 | AtomicReference, AtomicStampedReference, AtomicMarkableReference |
原子更新对象引用,解决ABA问题。 |
| 对象属性 | AtomicIntegerFieldUpdater, AtomicLongFieldUpdater, AtomicReferenceFieldUpdater |
原子更新对象的某个字段。 |
3. 实战演练:AtomicInteger
AtomicInteger 是最常用的原子类。让我们对比一下使用 int 和 AtomicInteger 在多线程下的表现。
场景:多线程计数器
假设有 100 个线程,每个线程对计数器累加 1000 次。
错误示范:普通 int
java
public class UnsafeCounter {
private static int count = 0;
public static void main(String[] args) throws InterruptedException {
Thread[] threads = new Thread[100];
for (int i = 0; i < 100; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++) {
count++; // 非原子操作:读 -> 改 -> 写
}
});
threads[i].start();
}
for (Thread t : threads) t.join();
// 预期结果:100000,实际结果通常小于 100000
System.out.println("Final Count (Unsafe): " + count);
}
}
正确示范:AtomicInteger
java
import java.util.concurrent.atomic.AtomicInteger;
public class SafeCounter {
// 使用原子类
private static AtomicInteger atomicCount = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
Thread[] threads = new Thread[100];
for (int i = 0; i < 100; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++) {
// 相当于 i++,但在硬件层面保证原子性
atomicCount.getAndIncrement();
}
});
threads[i].start();
}
for (Thread t : threads) t.join();
// 结果始终为 100000
System.out.println("Final Count (Atomic): " + atomicCount.get());
}
}
源码剖析:getAndIncrement
AtomicInteger 的 getAndIncrement 是如何实现的?
java
// JDK 8 源码片段
public final int getAndIncrement() {
// this: 当前对象
// valueOffset: value 字段在内存中的偏移量
// 1: 增加的值
return unsafe.getAndAddInt(this, valueOffset, 1);
}
// Unsafe 类中的实现 (模拟)
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
// 1. 获取内存中当前的值
v = getIntVolatile(o, offset);
// 2. 循环尝试 CAS 操作
// 如果内存值还是 v,则更新为 v + delta,返回 true 退出循环
// 否则(说明被其他线程改了),返回 false,继续循环(自旋)
} while (!compareAndSwapInt(o, offset, v, v + delta));
return v;
}
5. 高性能利器:LongAdder (JDK 8+)
虽然 AtomicLong 使用 CAS 保证了原子性,但在超高并发下,大量线程同时竞争更新同一个变量,会导致大量 CAS 失败并自旋,消耗 CPU 资源。
JDK 8 引入了 LongAdder。
设计思想:分段锁(空间换时间)
LongAdder 内部维护了一个 base 变量和一个 Cell[] 数组。
- 无竞争时:直接更新
base。 - 有竞争时:线程被哈希映射到
Cell数组的某个槽位,对该槽位的值进行 CAS 更新。 - 获取最终结果时:
sum = base + sum(Cell[])。
AtomicLong vs LongAdder 性能对比示意
LongAdder
CAS
Hash
Hash
Hash
Thread A
Base
Thread B
Cell 1
Thread C
Cell 2
Thread D
AtomicLong
CAS 竞争
CAS 竞争
CAS 竞争
CAS 竞争
Thread 1
Value
Thread 2
Thread 3
Thread 4
代码示例
java
import java.util.concurrent.atomic.LongAdder;
public class LongAdderDemo {
public static void main(String[] args) {
LongAdder adder = new LongAdder();
// 多个线程同时累加
adder.increment();
adder.add(10);
// 获取总和
System.out.println("Sum: " + adder.sum());
}
}
注意: LongAdder 的 sum() 方法返回的值只是一个近似准确值(在并发极高时,统计过程中可能有新的累加发生),但在统计计数等场景下完全够用且性能极佳。