CAS是Compare And Swap。它是java.util.concurrent包的基石。
我们知道,如果线程直接对volatile变量赋值(原子性操作),也是可以实现线程安全的,但更多情况,我们线程对volatile变量的修改与volatile变量的当前值是有关系的!!,这就需要先对这个volatile变量读,然后再写,这就有了至少两个步骤了,所以这个操作不是原子性的,所以就不能保证线程安全。

由此,java使用CAS操作,通过底层实现将变量读-变量写过程变为原子执行!其利用到了缓存锁定,通过将其他各个线程中的该变量的缓存锁定,不允许操作,然后在本线程对该变量进行修改,然后再刷入主内存。

下面以AtomicInteger来研究CAS的具体实现过程。


/**
*  AtomicInteger.java
*/
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;

static{
    try{
       .....初始化valueOffset.....
    }
}

private volatile int value;


public final int get(){
    return value;
}

public final int incrementAndGet(){
    return unsafe.getAndAddInt(this, valueOffset, 1);
}
/**
*  Unsafe.java
*  getIntVolatile 方法和 compareAndSwapInt 为本地方法调用
*  o 为需要修改的对象
*  offset为需要修改字段到对象头的偏移量
*  delta为更新增加量
*  expected为期望值
*  x为更新值
*/

public final int getAndInt(Object o, long offset, int delta){

    int v;
    do {
        v = getIntVolatile(o, offset);
    }while (!compareAndSwapInt(o, offset, v, v + delta));
    return v;
}


public native int getIntVolatile(Object o, long offset);

// 该方法即为CAS,它借助C语言调用CPU底层指令实现,大致流程是 判断对象o中要修改的字段的值是否为 expected。当相同时,将x值写入o
public native boolean compareAndSwapInt(Object o, long offset, int expected, int x);
/**
* compareAndSwapInt本地方法的源代码片段
*/

// mp为处理器核心数量
int mp = os::is_MP();
__asm {
    mov edx, dest
    mov ecx, exchange_value
    mov eax, compare_value
    
    //  带有此LOCK前缀的指令在执行期间可以保证执行的原子性(通过锁缓存)、禁止指令重排序、将写缓冲区中所有数据刷新到内存
    LOCK_IF_MP(mp)

    // cmpxchg 指令 将AL/AX/EAX/RAX寄存器中的值与首操作数比较,如果相等,则将第二操作数的值装载到首操作数  由于edx装的是指针,所以这里首操作数为 dword ptr [edx],表明这该指针指向的单元是一个double word (双字单元), 即4字节。
    cmpxchg dword ptr [edx], ecx
}

LOCK前缀有如下说明:

  • 确保对内存的读写操作原子执行(在这里使cmpxchg原子执行)
  • 禁止该指令(cmpxchg)与之前和之后的读和写指令重排序 (即 使其具有volatile读、写语义)
  • 把写缓冲区的所有数据刷到内存中 (上一条的实现,必须具备的能力)

综上所述,可以得出结论:

  • CAS操作的底层实现用到了LOCK前缀和cmpxchg指令,它将CAS成为一个原子操作
最后修改:2021 年 04 月 22 日
如果觉得我的文章对你有用,请随意赞赏