Java中的锁 (2) 底层CAS

本文深入讲解了CAS(Compare and Swap)的基本概念、工作原理及其在Java中的应用。CAS是实现原子操作的基础,对于理解Java并发编程至关重要。文章还探讨了CAS的优缺点,并通过AtomicInteger类的具体实现展示了如何使用CAS来确保线程安全。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

CAS

CAS:Compare and Swap, 翻译成比较并交换。

java.util.concurrent包完全建立在CAS之上的,没有CAS就不会有此包。CAS在多数CPU中只是一条指令,单挑指令是不会被中断的,因此保证了原子性;


CAS应用

CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。

简单来说:修改在一个变量a,它原来的值是1,所以你预期它是1,如果在你修改的时候,它被别的线程更新为2,那么就不符合你的预期,你的修改也不会生效。

CAS是通过unsafe类的compareAndSwapInt方法实现的

public final boolean compareAndSet(int expect, int update) {   
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

unsafe.compareAndSwapInt(this, valueOffset, expect, update);
第一个参数是要修改的对象,第二个参数是对象中要修改变量在内存中的偏移量,第三个参数是修改之前的值,第四个参数是预想修改后的值。

Unsafe是CAS的核心类,Java无法直接访问底层操作系统,而是通过本地(native)方法来访问。不过尽管如此,JVM还是开了一个后门,JDK中有一个类Unsafe,它提供了硬件级别的原子操作。valueOffset表示的是变量值在内存中的偏移地址,因为Unsafe就是根据内存偏移地址获取数据的原值的。

先看一段代码,CAS原理就类似与这个

public int this = expect;
public boolean compareAndSwapInt(int update) {
    if (this == expect) {
        this = update;
        return true;
    }
    return false;
}

试想这段代码在多线程并发下,会发生什么?我们不妨来分析一下:

1)线程A执行到 this==expect,正准备执行this = update时,线程B也正在运行this = update,并在线程A之前把this修改为2;最后线程A又把this修改成了3。结果就是两个线程同时修改了变量this,显然这种结果是无法符合预期的,无法确定this的值。
2)解决方法也很简单,在compareAndSwapInt方法加锁同步,变成一个原子操作,同一时刻只有一个线程才能修改变量a。

CAS中的比较和替换是一组原子操作,不会被外部打断,先根据valueOffset(内存偏移地址)获取到内存当中当前的内存值V,在将内存值V和原值A作比较,要是相等就修改为要修改的值B,属于硬件级别的操作,效率比加锁操作高。

java.util.concurrent.atomic包下的原子操作类都是基于CAS实现的,接下去我们通过AtomicInteger来看看是如何通过CAS实现原子操作的:

public class AtomicInteger extends Number implements java.io.Serializable {
    // setup to use Unsafe.compareAndSwapInt for updates
    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 get() {return value;}
}

value是用volatile修饰的,保证了多线程之间看到的value值是同一份。

接下去,我们看看AtomicInteger是如何实现并发下的累加操作:

//jdk1.8实现
public final int getAndAdd(int delta) {    
    return unsafe.getAndAddInt(this, valueOffset, delta);
}

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;
}

在jdk1.8中,比较和替换操作放在unsafe类中实现。

假设现在线程A和线程B同时执行getAndAdd操作:

AtomicInteger里面的value原始值为3,即主内存中AtomicInteger的value为3,根据Java内存模型,线程A和线程B各自持有一份value的副本,值为3。

线程A通过getIntVolatile(var1, var2)方法获取到value值3,线程切换,线程A挂起。

线程B通过getIntVolatile(var1, var2)方法获取到value值3,并利用compareAndSwapInt方法比较内存值也为3,比较成功,修改内存值为2,线程切换,线程B挂起。

线程A恢复,利用compareAndSwapInt方法比较,发现手里的值3和内存值2不一致,此时value正在被另外一个线程修改,线程A不能修改value值。

线程的compareAndSwapInt实现,循环判断,重新获取value值,因为value是volatile变量,所以线程对它的修改,线程A总是能够看到。线程A继续利用compareAndSwapInt进行比较并替换,直到compareAndSwapInt修改成功返回true。

整个过程中,利用CAS保证了对于value的修改的线程安全性。


CAS缺点

CAS存在一个很明显的问题,即ABA问题。
如果变量V初次读取的时候是A,并且在准备赋值的时候检查到它仍然是A,那能说明它的值没有被其他线程修改过了吗?如果在这段期间它的值曾经被改成了B,然后又改回A,那CAS操作就会误认为它从来没有被修改过。针对这种情况,java并发包中提供了一个带有标记的原子引用类”AtomicStampedReference”,它可以通过控制变量值的版本来保证CAS的正确性。

全篇参考 占小狼 的文章
链接:http://www.jianshu.com/p/fb6e91b013cc

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值