一、CAS概述
CAS(Compare And Swap,比较并交换)是Java并发编程中的一种无锁原子操作技术,它是现代多核处理器提供的一种原子指令,也是Java并发包中许多高性能类(如AtomicInteger、ConcurrentHashMap等)的基础实现机制。
1.1 CAS基本概念
CAS操作包含三个操作数:
- 内存位置(V)
- 预期原值(A)
- 新值(B)
CAS操作逻辑:当且仅当内存位置V的值等于预期原值A时,处理器才会将该位置的值更新为新值B,否则不执行任何操作。无论哪种情况,都会返回该内存位置的当前值。
二、Java中的CAS实现
在Java中,CAS操作主要通过sun.misc.Unsafe类提供的本地方法实现,这些方法最终会调用CPU的原子指令。
2.1 Unsafe类中的CAS方法
在JDK 1.8的Unsafe类中,主要提供了以下几种CAS操作方法:
public final native boolean compareAndSwapObject(Object o, long offset, Object expected, Object x);
public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x);
public final native boolean compareAndSwapLong(Object o, long offset, long expected, long x);
这些方法都是native方法,它们的实现依赖于JVM和底层硬件。
2.2 AtomicInteger的CAS实现
以AtomicInteger为例,我们来看Java中如何利用CAS实现原子操作:
public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L;
// 获取Unsafe实例
private static final Unsafe unsafe = Unsafe.getUnsafe();
// value字段的内存偏移量
private static final long valueOffset;
static {
try {
// 获取value字段在AtomicInteger对象中的内存偏移量
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
private volatile int value;
// CAS操作的核心方法
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
// 原子递增操作
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
}
2.3 Unsafe.getAndAddInt实现
在JDK 1.8中,Unsafe.getAndAddInt的实现如下:
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = getIntVolatile(o, offset);
} while (!compareAndSwapInt(o, offset, v, v + delta));
return v;
}
这是一个典型的CAS自旋操作:
- 首先获取当前值v
- 然后尝试用CAS将值更新为v+delta
- 如果失败(说明有其他线程修改了该值),则循环重试
三、CPU层面的CAS实现
Java的CAS操作最终会映射到CPU的原子指令。不同架构的CPU有不同的实现:
3.1 x86架构的实现
在x86架构中,CAS操作对应的是cmpxchg指令(compare and exchange)。这个指令会:
- 比较目标内存位置的值与寄存器中的值
- 如果相等,则将新值写入内存位置
- 无论是否相等,都会返回内存位置的原始值
3.2 多处理器下的实现
在多处理器环境下,cmpxchg指令需要加上lock前缀来保证原子性:
lock cmpxchg指令会在执行期间锁定总线或缓存行,防止其他处理器同时访问该内存位置- 现代CPU通常使用缓存一致性协议(如MESI)来优化这一过程,而不是真正锁定总线
四、CAS在Java并发类中的应用
4.1 Atomic原子类
Java并发包中的原子类(AtomicInteger、AtomicLong等)都基于CAS实现:
public class AtomicLong extends Number implements java.io.Serializable {
public final long incrementAndGet() {
return unsafe.getAndAddLong(this, valueOffset, 1L) + 1L;
}
}
4.2 ConcurrentHashMap
JDK 1.8中的ConcurrentHashMap大量使用了CAS操作:
final V putVal(K key, V value, boolean onlyIfAbsent) {
// ...
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
// 使用CAS尝试将新节点放入空桶中
if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null)))
break;
}
// ...
}
4.3 AQS(AbstractQueuedSynchronizer)
Java并发包的核心AQS也使用了CAS来实现状态变更:
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
五、CAS的优缺点
5.1 优点
- 无锁:避免了传统锁机制带来的线程阻塞和上下文切换开销
- 高性能:在低竞争环境下性能优异
- 无死锁:因为不使用锁,所以不会产生死锁
5.2 缺点
- ABA问题:一个值从A变成B又变回A,CAS会认为它没有被修改过
-
- 解决方案:使用版本号(AtomicStampedReference)
- 循环时间长开销大:在高竞争环境下,CAS失败会不断重试
- 只能保证一个共享变量的原子操作
-
- 解决方案:将多个变量合并为一个(如高低位),或使用AtomicReference
六、CAS的ABA问题及解决方案
6.1 ABA问题示例
AtomicInteger atomicInt = new AtomicInteger(1);
// 线程1
int value = atomicInt.get(); // 获取1
// 线程2
atomicInt.compareAndSet(1, 2); // 1→2
atomicInt.compareAndSet(2, 1); // 2→1
// 线程1
atomicInt.compareAndSet(1, 3); // 成功,但中间经历了变化
6.2 解决方案:AtomicStampedReference
AtomicStampedReference<Integer> atomicStampedRef =
new AtomicStampedReference<Integer>(1, 0);
// 线程1
int stamp = atomicStampedRef.getStamp();
Integer value1 = atomicStampedRef.getReference();
// 线程2
atomicStampedRef.compareAndSet(1, 2, stamp, stamp+1);
atomicStampedRef.compareAndSet(2, 1, stamp+1, stamp+2);
// 线程1
boolean success = atomicStampedRef.compareAndSet(value1, 3, stamp, stamp+1);
// success=false,因为stamp已经改变
七、JDK 1.8对CAS的优化
JDK 1.8引入了一些针对高竞争环境的优化:
7.1 LongAdder
对于高并发的计数场景,LongAdder比AtomicLong性能更好:
public class LongAdder extends Striped64 implements Serializable {
public void increment() {
add(1L);
}
public void add(long x) {
Cell[] as; long b, v; int m; Cell a;
if ((as = cells) != null || !casBase(b = base, b + x)) {
// 使用分段CAS减少竞争
// ...
}
}
}
7.2 消除伪共享
JDK 1.8的@Contended注解可以帮助消除伪共享:
@sun.misc.Contended
static final class Cell {
volatile long value;
Cell(long x) { value = x; }
// ...
}
八、总结
CAS作为Java并发编程的核心机制,其实现涉及从Java代码到JVM再到CPU指令的多层协作。理解CAS的工作原理对于编写高性能并发程序至关重要。JDK 1.8在CAS的基础上进行了诸多优化,使得Java并发编程在高竞争环境下也能保持良好的性能表现。
在实际开发中,我们应该:
- 在低竞争环境下优先使用CAS-based类(如AtomicInteger)
- 在高竞争环境下考虑使用LongAdder等优化类
- 注意ABA问题,必要时使用带版本号的原子引用
- 理解CAS的适用场景,不盲目使用无锁编程
Java中CAS实现原理深度剖析
176

被折叠的 条评论
为什么被折叠?



