03 原子操作CAS(Compare And Swap)

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 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))