美文网首页
深入理解 Java 中的 CAS

深入理解 Java 中的 CAS

作者: 张贤同学 | 来源:发表于2020-04-08 14:46 被阅读0次

    Java 中的synchronized是一种悲观锁,悲观锁始终假设会发生并发冲突,因此会阻止一切可能违反数据完整性的操作。而 CAS 是一种乐观锁
    CAS 全称是 Compare and Swap。乐观锁假设不会发生并发冲突,因此只在提交的时候检查是否违反数据完整性,如果提交失败则会进行重试。

    CAS 支持原子更新操作,适用于计数器,序列发生器等场景。CAS 操作失败时由开发者决定是继续尝试,还是执行别的操作。

    CAS 的思想:
    CAS机制当中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。

    更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B。

    public class CASCase {
        public volatile int value;
    
        public void add(){
            value++;
        }
    }
    

    使用javap查看编译后的add()方法中的value++的字节码如下:

    
    2: getfield      #2                  // Field value:I
    5: iconst_1
    6: iadd
    7: putfield      #2                  // Field value:I
    

    可以看到value++被分成了三步,虽然value变量使用了volatile修饰,对value++操作禁止了指令重排序,但是value++不是原子性操作,在多线程并发下还是会产生线程安全问题。一种解决的办法是给add()方法加上synchronized关键字,但是synchronized属于悲观锁,是否能够进一步提升性能呢?其实还可以使用AtomicInteger来满足我们的要求

    public class CASCase {
        public AtomicInteger value;
    
        public void add(){
            value.getAndIncrement();
        }
    }
    

    getAndIncrement()方法底层调用的是Unsafe类的compareAndSetInt方法,用到的就是 CAS 的思想。

    CAS 多数情况下对开发者来说是透明的。J.U.C 的 atomic 包提供了常用了原子性数据类型以及引用、
    数组等相关原子类型和更新操作工具,是很多线程安全程序的首选。Unsafe 类虽然提供 CAS 服务,但是因为能够操纵任意内存地址读写而有隐患。在 Java9 之后,可以使用 Variable Handle API 来替代 Unsafe。

    CAS 虽然很高效,但是也有缺点:

    • CAS 若循环时间长,则开销很大。我们可以看到在Unsafe类的getAndAddInt()执行时,如果 CAS 失败,会一直循环尝试进行 CAS 赋值,如果长时间都在循环,会给 CPU 带来很大开销
    • CAS 只能保证一个共享变量的原子操作,但无法保证对多个共享变量的原子性操作
    • ABA 问题。如果一个变量在进行 CAS 操作第一次读取时是 A,中间被其他操作改成了 B,又改回了 A,等到 CAS 操作第二次读取的时候也是 A,CAS 就会误认为这个变量从来没有被改变过。在一些场景中,ABA 问题可能会影响程序的并发正确性。为了解决这个问题,J.U.C 提供了AtomicStampedReference,可以通过变量值的版本来保证 CAS 的正确性。不过在 ABA 问题下,可能用传统的 加锁互斥的方法更加高效。



      原文发表于:https://zhangxiann.com/archives/cas
      欢迎关注我的博客:zhangxiann.com

    相关文章

      网友评论

          本文标题:深入理解 Java 中的 CAS

          本文链接:https://www.haomeiwen.com/subject/lkstmhtx.html