Java中的CAS实现原理深度解析

Java中CAS实现原理深度剖析

一、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自旋操作:

  1. 首先获取当前值v
  2. 然后尝试用CAS将值更新为v+delta
  3. 如果失败(说明有其他线程修改了该值),则循环重试

三、CPU层面的CAS实现

Java的CAS操作最终会映射到CPU的原子指令。不同架构的CPU有不同的实现:

3.1 x86架构的实现

在x86架构中,CAS操作对应的是cmpxchg指令(compare and exchange)。这个指令会:

  1. 比较目标内存位置的值与寄存器中的值
  2. 如果相等,则将新值写入内存位置
  3. 无论是否相等,都会返回内存位置的原始值

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 优点

  1. 无锁:避免了传统锁机制带来的线程阻塞和上下文切换开销
  2. 高性能:在低竞争环境下性能优异
  3. 无死锁:因为不使用锁,所以不会产生死锁

5.2 缺点

  1. ABA问题:一个值从A变成B又变回A,CAS会认为它没有被修改过
    • 解决方案:使用版本号(AtomicStampedReference)
  1. 循环时间长开销大:在高竞争环境下,CAS失败会不断重试
  2. 只能保证一个共享变量的原子操作
    • 解决方案:将多个变量合并为一个(如高低位),或使用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

对于高并发的计数场景,LongAdderAtomicLong性能更好:

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并发编程在高竞争环境下也能保持良好的性能表现。

在实际开发中,我们应该:

  1. 在低竞争环境下优先使用CAS-based类(如AtomicInteger)
  2. 在高竞争环境下考虑使用LongAdder等优化类
  3. 注意ABA问题,必要时使用带版本号的原子引用
  4. 理解CAS的适用场景,不盲目使用无锁编程
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

凡尘扰凡心

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值