美文网首页
深入刨析CAS

深入刨析CAS

作者: Yangsc_o | 来源:发表于2020-06-07 22:38 被阅读0次

[toc]



摘要

本文从CAS的基本操作开始,逐步探究CAS的实现原理,本文涉及代码使用JDK1.8版本;

CAS是什么?

CAS是Compare And Swap (Compare And Exchange) 的简称,从因为的意思也很容易理解:比较并交换。

  • 先看一段代码,两个线程分别对atomicInteger加100,因为AtomicInteger是可以保证++是原子操作的,所以最终输出结果是:200
public class CasDemo {
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(0);
        new Thread(()->{
            for (int i = 0; i < 100; i++) {
                atomicInteger.incrementAndGet();
            }
        },"a").start();
        new Thread(()->{
            for (int i = 0; i < 100; i++) {
                atomicInteger.incrementAndGet();
            }
        },"b").start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info(atomicInteger.get());
    }
}

CAS是如何实现的?

  • AtomicInteger类
    在AtomicInteger数据定义的部分,实际存储的值是放在value中的,除此之外获取了unsafe实例,并且定义了valueOffset。再看到static块,根据加载过程,static块的加载发生于类加载的时候,是最先初始化的,这时候调用unsafe的objectFieldOffset从Atomic类文件中获取value的偏移量,那么valueOffset其实就是记录value的偏移量的。
public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;
    // 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;
    ...
}
  • 再看一下incrementAndGet函数
  • var5 = this.getIntVolatile(var1, var2); // 取出Object中偏移地址为var2的值var5;
  • this.compareAndSwapInt(var1, var2, var5, var5 + var4)比较var1中偏移量为var2的值是否和var5相等?相等则更新为var5 + var4;参数换个名字应该会清晰很多:compareAndSwapInt(obj, offset, expect, update);
 /**
 * Atomically increments by one the current value.
 *
 * @return the updated value
 */
public final int incrementAndGet() {
    return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}

public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
        // 取出Object中偏移地址为var2的值var5;
        var5 = this.getIntVolatile(var1, var2);
        // 比较var1中偏移量为var2的值是否和var5相等?相等则更新为var5 + var4;
    } while (!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

    return var5;
}
  • 问题来了?比较并交换就是也是两个步骤,怎么能保证线程同步呢?
    下载一下Hotspot源码,看到compareAndSwapInt实现的源码如图所示,发现最终调用了Atomic::cmpxchg(x, addr, e)方法。

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);


JNI

翻看源码(如下面两张图所示)可以看到,不同的平台有不同的实现方式;

  • 在x86的架构下实现,是通过LOCK_IF_MP加锁的方式实现
  • os::is_MP判断当前系统是否为多核系统,如果是就给总线加锁,所以同一芯片上的其他处理器就暂时不能通过总线访问内存,保证了该指令在多处理器环境下的原子性。
  • asm说明是ASM汇编,volatile禁止编译器优化,
// Adding a lock prefix to an instruction on MP machine
#define LOCK_IF_MP(mp) "cmp $0, " #mp "; je 1f; lock; 1: "
cmpxchg

在atomic.cpp中则是通过递归实现的;

因为根据IA64手册,X86_64架构下,不跨越cacheline的8byte读写是原子的,如果你有个指针,没有跨越cacheline,那么多线程对这个指针的复制和读取都是不需要加锁的,可以保证原子的读到这8byte;

在这里插入图片描述

CAS存在的问题?

  • ABA的问题
    CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。这就是CAS的ABA问题。
    常见的解决思路是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A。
    目前在JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。

  • 循环时间长开销大
    如果CAS不成功,则会原地自旋,如果长时间自旋会给CPU带来非常大的执行开销。


你的鼓励也是我创作的动力

打赏地址

相关文章

  • 深入刨析CAS

    [toc] Posted by 微博@Yangsc_o 原创文章,版权声明:自由转载-非商用-非衍生-保持署名 |...

  • 深入刨析AQS

    [toc] Posted by 微博@Yangsc_o 原创文章,版权声明:自由转载-非商用-非衍生-保持署名 |...

  • 深入刨析park、unpark

    [toc] Posted by 微博@Yangsc_o 原创文章,版权声明:自由转载-非商用-非衍生-保持署名 |...

  • 最通俗易懂的 HashMap 源码分析解读

    HashMap 作为最常用的集合类之一,有必要深入浅出的了解一下。这篇文章会深入到 HashMap 源码,刨析它的...

  • 深入刨析volatile关键词

    [toc] Posted by 微博@Yangsc_o 原创文章,版权声明:自由转载-非商用-非衍生-保持署名 |...

  • 自我刨析

    沉下心来思考,接受的教育、所处的环境、社会文化的熏陶,正面的东西确实不是很多,特别是出了社会以后更加如此,但是更令...

  • 自我刨析

    总感觉每天都很忙,总感觉每天的时间都不够用。总在反问时间都去哪了?除了睡觉的那几个小时之外,几乎每个小时都是...

  • 自我刨析

    “文化属性”,我喜欢这个词的感觉。 我曾经多次预想过,退休后隐居山林。不过,这个计划都被山野之地的各种不...

  • 自我刨析

    近期待业,所以在思考自己的职业规划,职业规划第一步就是自我认知,只有知道自己是个什么样人,喜欢和适合做什么,才能定...

  • 自我刨析

    既然养成自卑胆小懦弱的性格,那就要改。 首先放开自我,没什么大不了。 其次锻炼意志,改掉懒惰。 神经质问题,把精力...

网友评论

      本文标题:深入刨析CAS

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