在之前的内容中已经讲述了 JMM内存模型与volatile,同时也讲述了保障线程安全的 同步互斥 与 异步非阻塞 两种方式,更描述了异步非阻塞中的 CAS
原理以及 CAS
的一些经典问题, 本文接下来继续完成我们的并发版图,了解下什么是 Atomic
。
原子类是 Java 并发编程的核心组件,位于 java.util.concurrent.atomic 包中。它们基于 CAS 机制实现无锁线程安全操作,在高并发场景下性能显著优于传统锁机制。
在线程安全的那篇文章中描述过什么是CAS,此处我们回顾下
翻译:Compare-and-Swap,简称 CAS
本质就是 乐观锁思想,也是无锁的思想
CAS指令需要三个操作数,分别是变量的内存地址,用V表示,旧值用A表示,新值用B表示。
CAS指令在执行时,当且当V符合A时,处理器才会用B更新V的值,否则就不更新不管是否更新了V值,都会返回V的旧值,这个处理过程是一个原子操作,执行期间不会被其他线程中断
java// 伪代码展示 CAS 原理
boolean compareAndSwap(V expectedValue, V newValue) {
if (this.value == expectedValue) {
this.value = newValue;
return true;
}
return false;
}
💡 CAS 优势:避免线程阻塞,减少上下文切换开销
⚠️ ABA 问题:值从 A→B→A 的变化无法被检测(解决方案:AtomicStampedReference)
常用方法
int addAndGet(int delta)
以原子方式将输入的数值与实例中的值(AtomicInteger里的 value)相加,并返回结果
boolean compareAndSet(int expect,int update)
如果输入的数值等于预期值,则以原子方 式将该值设置为输入的值
int getAndIncrement()
以原子方式将当前值加1,注意,这里返回的是自增前的值
int getAndSet(int newValue)
以原子方式设置为newValue的值,并返回旧值
核心源码分析
javapublic class AtomicInteger {
private volatile int value; // volatile保证可见性
// Unsafe类提供CAS底层操作
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
// 获取value字段的内存偏移地址
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
public final boolean compareAndSet(int expect, int update) {
// 调用Unsafe的CAS指令
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
}
执行流程图
代码示例
java// 原子整型计数器
AtomicInteger counter = new AtomicInteger(0);
// 原子自增(线程安全)
int newValue = counter.incrementAndGet(); // 等同于 ++i
int current = counter.getAndIncrement(); // 等同于 i++
// CAS 更新示例
boolean updated = counter.compareAndSet(5, 10);
// 当前值=5时更新为10
常用方法
int addAndGet(int i,int delta)
以原子方式将输入值与数组中索引i的元素相加。
boolean compareAndSet(int i,int expect,int update)
如果当前值等于预期值,则以原子 方式将数组位置i的元素设置成update值
代码示例
java// 原子整型数组
AtomicIntegerArray atomicArray = new AtomicIntegerArray(10);
// 原子更新数组元素
atomicArray.set(0, 100); // 设置索引0的值为100
int prev = atomicArray.getAndAdd(0, 5); // 索引0的值+5,返回旧值
// CAS 更新数组
boolean success = atomicArray.compareAndSet(0, 105, 110);
java// 原子引用
AtomicReference<User> userRef = new AtomicReference<>();
User user1 = new User("Alice");
User user2 = new User("Bob");
userRef.set(user1);
// 原子更新引用
boolean updated = userRef.compareAndSet(user1, user2);
// 初始值=100, 版本号=0
AtomicStampedReference<Integer> money = new AtomicStampedReference<>(100, 0);
// 更新时检查值和版本号
int[] stampHolder = new int[1];
int currentStamp = money.getStamp();
int currentValue = money.getReference();
// 版本号+1防止ABA问题
boolean success = money.compareAndSet(
currentValue,
currentValue + 50,
currentStamp,
currentStamp + 1
);
使用注意
对象的属性修改类型都是抽象类,所以在使用之前必须使用静态方法newUpdater创建一个新的更新器,然后参数传入需要更新的类和属性,属性名同时待修改的属性必须用public volatile关键字修饰
javapublic class AtomicIntegerFieldUpdaterTest {
// 创建原子更新器,并设置需要更新的对象类和对象的属性
private static AtomicIntegerFieldUpdater<User> a = AtomicIntegerFieldUpdater.
newUpdater(User.class, "old");
public static void main(String[] args) {
// 设置柯南的年龄是10岁
User conan = new User("conan", 10);
// 柯南长了一岁,但是仍然会输出旧的年龄
System.out.println(a.getAndIncrement(conan));
// 输出柯南现在的年龄
System.out.println(a.get(conan));
}
public static class User {
private String name;
// 必须volatile修饰
public volatile int old;
public User(String name, int old) { this.name = name; this.old = old; }
}
}
目的是解决高并发环境下 AtomicLong 这种基于CAS算法的原子类自旋瓶颈问题,内部采用 分段CAS机制,高并发下性能优于 AtomicLong
LongAccumulator:long类型的聚合器,需要传入一个long类型的二元操作,可以用来计算各种聚合操作,包括加乘等
LongAdder:long类型的累加器,LongAccumulator的特例,只能用来计算加法,且从0开始计算
DoubleAccumulator:double类型的聚合器,需要传入一个double类型的二元操作,可以用来计算各种聚合操作,包括加乘等
DoubleAdder double类型的累加器,DoubleAccumulator的特例,只能用来计算加法,且从0开始计算
Striped64:公共父类 Striped64 是实现中的核心,它实现一些核心操作,处理64位数据,很容易就能转化为其他基本类型,是个通用的类。
实现思想
采用分段的思想,提高CAS操作的成功率,AtomicLong中的成员变量value用来保存值,而在高并发的情况下value就变成了一个热点数据,也就是众多线程竞争一个 value ,将value值的操作分散到一个数组中,不同线程会命中到数组的不同槽中,各个线程只对自己槽中的那个值进行CAS操作,最后再把这些段的值相加得到最终的值。
代码示例
java// 高性能累加器(适合高并发统计)
LongAdder adder = new LongAdder();
// 多线程并发累加
parallelStream().forEach(i -> {
adder.add(i);
});
// 获取结果(最终一致性)
long total = adder.sum();
为什么会有高性能原子类
AtomicLong 通过CAS算法提供了非阻塞的原子性操作,由于过多线程同时去竞争同一个变量的更新从而导致无限循环的自旋锁不停的尝试,这就是高并发下CAS性能低下的原因所在,所以推出了高性能原子类。
原子类核心是基于 CAS 实现无锁线程安全,在低竞争场景性能远超传统锁,不同的场景下使用不同的类型体系:基本类型、引用类型、数组类型、对象属性修改类型,以及在高并发的场景下使用的高性能原子类类型。代码最佳实践如下:
java// 1. 简单计数
private AtomicInteger requestCount = new AtomicInteger(0);
// 2. 状态机更新
private AtomicReference<State> state = new AtomicReference<>(State.INIT);
// 3. 高并发统计
private LongAdder totalBytes = new LongAdder();
原子类作为 JUC 包的核心组件,合理使用可大幅提升并发程序性能,但需根据具体场景权衡其与锁机制的选用。
本文作者:柳始恭
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!