一、什么是CAS?为什么我们需要它?
在多线程并发编程中,共享资源的原子性操作是一个经典难题。传统的synchronized
锁虽然能保证线程安全,但会带来线程阻塞和唤醒的性能开销。这时CAS(Compare And Swap)作为一种无锁算法应运而生。
CAS的核心思想:我认为V的值应该是A,如果是的话我就把它改成B;如果不是A,说明有人改过,我就不修改了。这个比较和交换的过程是一个原子操作,由CPU指令直接保证。
二、深入CAS底层实现
2.1 Java中的Unsafe类
在HotSpot JVM中,CAS操作通过sun.misc.Unsafe
类实现:
java
public final class Unsafe {
public final native boolean compareAndSwapInt(
Object o, long offset, int expected, int x);
// 其他CAS方法...
}
这个方法参数含义:
- o:要操作的对象
- offset:字段在对象中的偏移量
- expected:预期原值
- x:新值
2.2 AtomicInteger源码解析
以常用的AtomicInteger为例,看其如何实现原子递增:
java
public class AtomicInteger extends Number {
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
private volatile int value;
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
}
Unsafe.getAndAddInt()的底层实现:
c++
UNSAFE_ENTRY(jint, Unsafe_GetAndAddInt(
JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint addValue)) {
oop p = JNIHandles::resolve(obj);
jint* addr = (jint*)index_oop_from_field_offset_long(p, offset);
return Atomic::add(addValue, addr);
} UNSAFE_END
可以看到最终调用的是CPU的原子指令(如x86的lock cmpxchg
)。
三、开发实战中的CAS应用
3.1 高性能计数器
统计接口调用次数,使用AtomicLong:
java
public class ApiCounter {
private final AtomicLong counter = new AtomicLong(0);
public void invoke() {
// 业务逻辑...
counter.incrementAndGet();
}
public long getCount() {
return counter.get();
}
}
3.2 实现自旋锁
通过CAS实现简单的自旋锁:
java
public class SpinLock {
private AtomicReference<Thread> owner = new AtomicReference<>();
public void lock() {
Thread current = Thread.currentThread();
while (!owner.compareAndSet(null, current)) {
// 自旋等待
}
}
public void unlock() {
Thread current = Thread.currentThread();
owner.compareAndSet(current, null);
}
}
3.3 实现无阻塞栈
使用CAS实现线程安全栈:
java
public class ConcurrentStack<E> {
private AtomicReference<Node<E>> top = new AtomicReference<>();
public void push(E item) {
Node<E> newHead = new Node<>(item);
Node<E> oldHead;
do {
oldHead = top.get();
newHead.next = oldHead;
} while (!top.compareAndSet(oldHead, newHead));
}
public E pop() {
Node<E> oldHead;
Node<E> newHead;
do {
oldHead = top.get();
if (oldHead == null) return null;
newHead = oldHead.next;
} while (!top.compareAndSet(oldHead, newHead));
return oldHead.item;
}
private static class Node<E> {
final E item;
Node<E> next;
Node(E item) { this.item = item; }
}
}
四、CAS的ABA问题与解决方案
4.1 ABA问题演示
假设初始值为A:
- 线程1读取值为A
- 线程2修改A→B→A
- 线程1执行CAS:检测到值还是A,操作成功
虽然结果正确,但中间的状态变化可能引发问题。
4.2 使用AtomicStampedReference解决
通过版本号控制:
java
public class AbaDemo {
private static AtomicStampedReference<Integer> atomicRef =
new AtomicStampedReference<>(100, 0);
public static void main(String[] args) {
int stamp = atomicRef.getStamp();
// 线程1尝试修改100→101
new Thread(() -> {
boolean success = atomicRef.compareAndSet(
100, 101, stamp, stamp+1);
System.out.println("Thread1 CAS: " + success);
}).start();
// 线程2执行A→B→A
new Thread(() -> {
atomicRef.compareAndSet(100, 101, stamp, stamp+1);
atomicRef.compareAndSet(101, 100, stamp+1, stamp+2);
}).start();
}
}
五、CAS使用注意事项
- 自旋时间过长:CAS配合自旋使用时,需注意自旋次数限制
- 仅保证单个变量原子性:多个变量原子操作需使用其他手段
- 性能考量:在高争用场景下,CAS可能不如锁高效
- 平台依赖性:不同CPU架构实现可能不同
六、总结与思考
CAS作为无锁编程的基石,在Java并发包(java.util.concurrent)中被广泛应用。合理使用CAS可以:
- 实现高性能的非阻塞算法
- 减少线程上下文切换开销
- 避免死锁风险
但也要根据具体场景选择方案:对于低竞争、简单操作推荐使用CAS;高竞争、复杂操作可能需要结合锁机制。
欢迎点赞收藏!如果有更好的CAS应用场景或实现技巧,欢迎在评论区交流讨论~