【JavaEE】CAS操作

目录

▮CAS

▮基于CAS创造出来的原子类

▪AtomicInteger:原子整型

▪自旋锁的实现

▮CAS的aba问题

▪解决方案

▮相关面试题



▮CAS

        CAS全名:compare and swap。他是CPU上的一条指令。这条指令的逻辑是:比较内存R和寄存器A的值,若相等,则交换内存R和寄存器B的值。不过,在实际中,我们更加关注内存上的值,所以这个操作可以换种逻辑:比较内存R和寄存器A的值,若相等,则给R赋值为B

        创造出来这么个东西是为了什么呢?因为,CAS具有原子性,上面的比较和赋值是一步同时完成的,它只是CPU上的一条指令。一步完成又有什么作用呢?因为,CAS能不加锁就能实现线程安全,减少了锁的开销。要知道,在多线程中,这种比较和赋值中是很容易出现线程安全问题的,CAS的原子性,就是它最大的价值。

        以下就是CAS的伪代码实现,注意是伪代码实现逻辑

boolean CAS(内存R, 寄存器A, 寄存器B) {
    if (R == A) {
        R = B;
        return true;
    }
    return false;
}

•CAS在JVM里的实现原理(博主表示看不懂)

        1.java 的 CAS 利用的的是 unsafe 这个类提供的 CAS 操作;

        2.unsafe 的 CAS 依赖了的是 jvm 针对不同的操作系统实现的 Atomic::cmpxchg;

        3.Atomic::cmpxchg 的实现使用了汇编的 CAS 操作,并使用 cpu 硬件提供的 lock 机制保证其原子 性。


▮基于CAS创造出来的原子类

        CAS指令最大的价值就是它的原子性,利用这点,我们可以创造出许多带有原子性的东西。

▪AtomicInteger:原子整型

        int的增大和减小操作都是非原子的,涉及:获取值,计算值,赋予值,这三个CPU指令。这在多线程当中是容易出现安全问题的。所以,我们使用CAS,利用它的原子性来解决这个问题

        •示例:后置++操作

        以后置++为例,感受一下CAS的魅力。以下是代码实例,其中getAndIncrement()方法就代表着后置++操作。

class AtomicInteger {
    //赋予的值
    private int value;
    //此方法代表着后置++操作
    public int getAndIncrement() {
        int oldValue = value;
        while ( CAS(value, oldValue, oldValue+1) != true) {
            oldValue = value;
       }
        return oldValue;
   }
}

        这里要注意一点,这里面使用了while()循环。这是因为,在多线程环境中,oldValue刚刚得到了value的值后,value的值就可能会被其它线程所修改。此时的CAS操作也不会成功,所以使用while循环重复执行,在value值被修改后,重新赋值给oldValue。

        了解了后置++原理后,简单的写个操作示例

        •AtomicInteger的其它方法

        (转自:http://t.csdn.cn/8l3pS)

▪自旋锁的实现

        自旋锁所在的线程能围着一个锁对象一直请求,尽可能的在第一时间就拿到这个锁对象。但在判断锁对象是否有锁这方面,在多线程中是会出现问题的。因为判断对象是否有锁,涉及:取值、比较、赋值,这三个指令。此线程可能在判断锁对象是否上锁的过程中,锁对象被其它线程上锁,而导致此线程误判,去给一个已经上了锁的对象来上锁

        下面就是自旋锁实现的代码。

public class SpinLock {
    //持有锁对象的线程,为null则表示锁对象没有被上锁
    private Thread owner = null;
    public void lock(){
        // 通过 CAS 看当前锁是否被某个线程持有. 
        // 如果这个锁已经被别的线程持有, 那么就自旋等待. 
        // 如果这个锁没有被别的线程持有, 那么就把 owner 设为当前尝试加锁的线程. 
        while(!CAS(this.owner, null, Thread.currentThread())){
       }
   }
}

        可以看到,自旋锁会一直循环执行CAS,这是要消耗系统资源的。


▮CAS的aba问题

        int  A = 100;

        线程t1的执行逻辑是CAS(A,100,50)

        线程t2的执行逻辑是CAS(A,100,50)

        线程t1执行后,A就变成了50,而线程t2的CAS就会执行失败。如果有其它线程,在t1把A改成50后,又把A改成100,使得t2得以执行。线程t2这里就可能会出现BUG,因为线程t2可能不该执行这个CAS。aba问题就是一个值从a变成了b,又从b变回了a。虽然CAS命令依旧可以执行,但这个变换过的A值,可能在逻辑上出现了一些错误

        举个示例。小明卡里的余额还有100,小明想去ATM里取走50。在第一次取钱操作时,ATM卡了一下,取钱迟迟没有反应;对此,小明开始了第二次取钱操作,在他刚刚操作好第二次前,第一次取钱操作被启动了,卡里的余额只剩50,那么这第二次取钱操作就会失败。这是一个正常的情况,问题是,如果在第一次取钱操作执行后,小明的朋友又给小明卡里打了50块钱,卡里的余额又变成了100,那么第二次取钱操作就会成功执行,这下就会执行两次取款操作,跟小明的预期不符,就会产生BUG。

▪解决方案

        给要修改的值, 引入版本号. 在 CAS 比较数据当前值和旧值的同时, 也要比较版本号是否符合预期 CAS 操作在读取旧值的同时, 也要读取版本号.。真正修改的时候, 如果当前版本号和读到的版本号相同, 则修改数据,并把版本号 + 1。 如果当前版本号高于读到的版本号. 就操作失败(认为数据已经被修改过了

        就比如上例中,小明的两次取钱操作的版本都是:扣钱01。在第一次取钱操作后,版本变成“取钱02”,那么第二次取钱操作的版本就会对不上,从而执行失败。

在 Java 标准库中提供了 AtomicStampedReference 类. 这个类可以对某个类进行包装, 在内部就提供了上面描述的版本管理功能. 关于 AtomicStampedReference 的具体用法此处不再展开. 有需要的自行查找。


▮相关面试题

        来自比特就业课的课件,做了一个搬运。

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值