2024-03-23
版权声明:本文为博主付费文章,严禁任何形式的转载和摘抄,维权必究。 本文链接:https://www.skjava.com/mianshi/baodian/detail/3366389036

CAS,Compare And Swap,即比较并交换,它是一种无锁编程技术的核心机制。但是你确定 CAS 是不加锁的吗?

我们先看 AtomicInteger 的 getAndIncrement() 源码:

    public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }
    
    public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;
    }
    
    public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

从这里我们可以看出,底层是利用 Unsafe#compareAndSwapInt() 实现的。

由于 Java 是无法直接访问底层操作系统的,只能通过本地(native)方法来访问。所以 Java 提供了 Unsafe 来实现,它提供了一系列的底层操作,允许 Java 代码执行一些通常不被安全策略允许的操作(绕过了 JVM)。这些操作包括直接内存访问、数组操作、线程的挂起与恢复、CAS 操作等。

从 Java 层面看,CAS 确实是无锁操作,但是在底层操作系统层面呢?

CAS 操作的原子性是由 CPU 提供保障的,依赖于硬件层面提供的原子指令来实现对共享数据的并发访问和修改。例如,比较常见的x86架构的 CPU 来说,其实 CAS 操作通常使用 cmpxchg (Compare and Exchange)指令实现的。这个指令的工作流程如下:

  1. 比较累加器(例如 EAX 寄存器)中的值与指定内存位置的值。
  2. 如果两者相等,指令将一个新值存入这个内存位置。
  3. 指令返回操作前该内存位置的旧值。

为了保证指令的原子性,处理器可能会使用锁定前缀(LOCK#)来锁定总线,防止其他处理器同时访问相同的内存地址。