美文网首页
03 原子操作CAS(Compare And Swap)

03 原子操作CAS(Compare And Swap)

作者: 攻城狮哦哦也 | 来源:发表于2019-08-13 16:10 被阅读0次

    1 什么是原子操作?如何实现原子操作?

    2 CAS的原理

    在计算机科学中,比较和交换(Conmpare And Swap)是用于实现多线程同步的原子指令。 它将内存位置的内容与给定值进行比较,只有在相同的情况下,将该内存位置的内容修改为新的给定值。 这是作为单个原子操作完成的。 原子性保证新值基于最新信息计算; 如果该值在同一时间被另一个线程更新,则写入将失败。 操作结果必须说明是否进行替换; 这可以通过一个简单的布尔响应(这个变体通常称为比较和设置),或通过返回从内存位置读取的值来完成(摘自维基本科)

    • CAS(Compare And Swap),指令级别保证这是一个原子操作
    • 三个运算符: 一个内存地址V,一个期望的值A,一个新值B
    • 基本思路:如果地址V上的值和期望的值A相等,就给地址V赋给新值B,如果不是,不做任何操作。
    • 循环(死循环,自旋)里不断的进行CAS操作

    3 JDK提供的CAS操作类

    • 更新基本类型类:AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference
    • 更新数组类:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
    • 更新引用类型:AtomicReference,AtomicMarkableReference,AtomicStampedReference
    • 原子更新字段类: AtomicReferenceFieldUpdater,AtomicIntegerFieldUpdater,AtomicLongFieldUpdater

    4 CAS在JUC中的运用

    JUC中非常重要的一个类AbstractQueuedSynchronizer,作为JAVA中多种锁实现的父类,其中有很多地方使用到了CAS操作以提升并发的效率.
    此段源码是往queue队列中添加node节点,利用了自旋和CAS原理

    /**
         * Inserts node into queue, initializing if necessary. See picture above.
         * @param node the node to insert
         * @return node's predecessor
         */
        private Node enq(final Node node) {
            for (;;) {
                Node t = tail;
                if (t == null) { // Must initialize
                    if (compareAndSetHead(new Node()))
                        tail = head;
                } else {
                    node.prev = t;
                    if (compareAndSetTail(t, node)) {
                        t.next = node;
                        return t;
                    }
                }
            }
        }
    

    上面为同步队列的入队操作,也是一种乐观锁的实现,多线程情况下,操作头节点和尾节点都有可能失败,失败后会再次尝试,直到成功。

    5 CAS的问题

    5.1 ABA问题

    什么是ABA问题
    如线程1从内存X中取出A,这时候另一个线程2也从内存X中取出A,并且线程2进行了一些操作将内存X中的值变成了B,然后线程2又将内存X中的数据变成A,这时候线程1进行CAS操作发现内存X中仍然是A,然后线程1操作成功。虽然线程1的CAS操作成功,但是整个过程就是有问题的。比如链表的头在变化了两次后恢复了原值,但是不代表链表就没有变化。
    解决方法

    • 使用版本号
      ABA问题的解决思路是使用版本号,每次变量更新的时候版本号加1,那么A->B->A就会变成1A->2B->3A
    • jdk自带原子变量
      从jdk1.5开始,jdk的Atomic包里就提供了一个类AtomicStampedReference来解决ABA问题,这个类中的compareAndSet方法的作用就是首先检查当前引用是否等于预期引用,并且检查当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值更新为指定的新值
    /**
         * 如果当前引用等于预期引用并且当前标志等于预期标志
         * 则以原子方式将该引用和该标志的值设置为给定新值
         *
         * @param expectedReference 预期引用值
         * @param newReference 新的引用值
         * @param expectedStamp 预期标记值
         * @param newStamp 新标记值
         * @return {@code true} if successful
         */
        public boolean compareAndSet(V   expectedReference,
                                     V   newReference,
                                     int expectedStamp,
                                     int newStamp) {
            Pair<V> current = pair;
            return
            #预期引用==当前引用
                expectedReference == current.reference &&
                #预期标志==当前标志
                expectedStamp == current.stamp &&
                #新引用==当前引用 并且 新标志==当前标志
                ((newReference == current.reference &&
                  newStamp == current.stamp) ||
                  #原子更新值
                 casPair(current, Pair.of(newReference, newStamp)));
        }
    
    

    5.2 开销问题

    循环时间长开销大
    自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。如果jvm能支持处理器提供的pause指令,那么效率会有一定的提升。pause指令有两个作用:

    第一,它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。

    第二,它可以避免在退出循环的时候因内存顺序冲突(Memory Order Violation)而引起CPU流水线被清空(CPU Pipeline Flush),从而提高CPU的执行效率。

    5.3 只能保证一个共享变量的原子操作

    CAS机制所保证的只是一个变量的原子性操作,而不能保证整个代码块的原子性。比如需要保证3个变量共同进行原子性的更新,就不得不使用Synchronized了。

    相关文章

      网友评论

          本文标题:03 原子操作CAS(Compare And Swap)

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