CAS的用法及原理

对于count++这种操作来说,使用synchronized成本太高了,需要先获取锁,最后需要释放锁,获取不到锁的情况下需要等待,还会有线程的上下文切换,这些都需要成本。

可以使用下面一系列原子变量。实际上,Java并发包中的所有阻塞式工具、容器、算法也都是基于CAS的。

AtomicBoolean:原子Boolean类型,常用来在程序中表示一个标志位。

AtomicInteger:原子Integer类型。

AtomicLong:原子Long类型,常用来在程序中生成唯一序列号。

AtomicReference:原子引用类型,用来以原子方式更新复杂类型。使用 CAS (Compare-And-Swap) 机制,但不是比较数值,而是比较对象引用的指针地址。最大的缺陷就是它会遇到 ABA 问题。该问题是指,假设当前值为A,如果另一个线程先将A修改成B,再修改回成A,当前线程的CAS操作无法分辨当前值发生过变化。这时候就需要使用AtomicStampedReference。

AtomicStampedReference:在compareAndSet中要同时修改两个值:一个是引用,另一个是时间戳。它怎么实现原子性呢?实际上,内部AtomicStampedReference会将两个值组合为一个对象,修改的是一个值。

AtomicLongArray:对 long[] 数组中每个元素的原子性操作,意味着你可以安全地在多线程环境下对数组的任一索引进行操作,而不需要额外的锁。

AtomicReferenceArray:提供一种原子性地更新引用类型数组元素的方式。就像是一个 Object[] 数组,但数组中的每一个元素都可以进行原子操作(如 CAS),从而保证在多线程环境下,对数组单个元素的读写是线程安全的。

AtomicIntegerFieldUpdater:以原子方式更新某个类中指定的 volatile int 字段,而无需将该字段声明为 AtomicInteger。提供了一种基于反射的、针对普通字段的原子操作能力。要求:不能是 static 字段、不能是 final 字段。特别适用于有大量需要原子操作的数字场景,此时只需要创建一个Updater对象即可。

AtomicLongFieldUpdater:同上。

AtomicReferenceFieldUpdater:同上。

LongAdder:高性能原子累加器。当并发量极高时,大量线程适用AtomicLong会因为 CAS 失败而不断自旋(重试),这不仅浪费 CPU 资源,还会导致严重的底层的缓存一致性流量(L1/L2 Cache 频繁失效),这被称为“热点冲突”。LongAdder 的核心思想是分散热点,通过分散竞争和解决伪共享来实现极致性能的。

DoubleAdder:同上。

LongAccumulator:LongAccumulatorLongAdder 的增强版,提供了更强大的功能:自定义计算规则。LongAdder 实际上可以看作是 LongAccumulator 的一个特例。在 Java 源码中,LongAdder 的逻辑就是固定了加法函数的 LongAccumulator

DoubleAccumulator:同上。

我们看下实现原理

这些方法的实现都依赖另一个public方法:

public final boolean compareAndSet(int expect, int update)

compareAndSet是一个非常重要的方法,比较并设置,我们以后将简称为CAS。

该方法有两个参数expect和update,以原子方式实现了如下功能:如果当前值等于expect,则更新为update,否则不更新,如果更新成功,返回true,否则返回false

AtomicInteger的incrementAndGet()函数实现:

private volatile int value;
public final int incrementAndGet() {
	for(; ; ) {
		int current = get();
		int next = current + 1;
		if(compareAndSet(current, next))
			return next;
	}
}

代码主体是个死循环,先获取当前值current,计算期望的值next,然后调用CAS方法进行更新,如果更新没有成功,说明value被别的线程改了,则再去取最新值并尝试更新直到成功为止。

与synchronized锁相比,这种原子更新方式代表一种不同的思维方式。

synchronized是悲观的,它假定更新很可能冲突,所以先获取锁,得到锁后才更新。

原子变量的更新逻辑是乐观的,它假定冲突比较少,但使用CAS更新,也就是进行冲突检测,如果确实冲突了,那也没关系,继续尝试就好了。

synchronized代表一种阻塞式算法,得不到锁的时候,进入锁等待队列,等待其他线程唤醒,有上下文切换开销。

原子变量的更新逻辑是非阻塞式的,更新冲突的时候,它就重试,不会阻塞,不会有上下文切换开销。

附:几种并发安全工具的性能对比

特性

synchronized

AtomicInteger

LongAdder (JDK 8+)

机制

悲观锁 (阻塞/挂起)

乐观锁 (CAS 自旋)

分段 CAS (空间换时间)

低并发性能

差 (有上下文切换开销)

极好

好 (有微小初始化开销)

高并发性能

一般 (线程排队,但不烧 CPU)

(大量 CAS 失败导致 CPU 100%)

极好 (吞吐量最高)

准确性

准确

准确

只能保证最终一致性 (sum() 时非原子快照)

适用场景

强一致性业务逻辑块

简单的计数器、标志位

高频统计 (如 Q

Java编程原理 文章被收录于专栏

知其然知其所以然,只有了解了底层原理,借助第一性原理,才可以运用自如,成为真大师。 什么是第一性原理? 第一性原理最早由亚里士多德提出,他将其定义为:“事物被已知的第一项前提。” 简单来说,它要求你不要用“类比”去思考(即:因为别人这样做,或者以前这样做,所以我也这样做),克服从众心理(FOMO)和经验偏差,在科技创新、商业决策中找到成本与效率的最优解。

全部评论
沾点喜气...
点赞 回复 分享
发布于 昨天 21:12 四川

相关推荐

评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务