Atomic实现原子性源码分析:CAS(比较并交换)、Unsafe类

1.CAS:
比较并交换(Compare And Swap),是Unsafe类中的一条CPU系统原语,原语的执行必须是连续的,在执行过程中不允许被中断,即CAS是一条CPU的原子指令,不会造成数据不一致问题。
类似:Atomic中的compareAndSet()方法
如果期望值和真实值相同,本次操作成功,如果不同则失败,这个过程是原子的
AtomicInteger atomicInteger = new AtomicInteger(5);
...
atomicInteger.compareAndSet(5, 2019); // 期望是5的时候,将其赋值为2019

2.Unsafe类:

rt.jar中的原生类,也是CAS的核心类,Unsafe类都是native修饰的,Unsafe类的方法都直接操作内存
以getAndIncrement()为例
atomicInteger.getAndIncrement();    // i++

AtomicInteger源码:

1)getAndIncrement()源码:

/**
 * Atomically increments by one the current value.
 *
 * @return the previous value
 */
public final int getAndIncrement() {
    return unsafe.getAndAddInt(this, valueOffset, 1);
}

valueOffset为变量值在内存中的偏移地址

2)AtomicInteger的value和valueOffset成员

// 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;

unsafe类源码:

public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
        var5 = this.getIntVolatile(var1, var2);
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

    return var5;
}

var1为当前AtomicInteger对象,var2是该对象值的引用地址,var4是需要增加的数量

其过程为:
1)首先在物理内存中获取当前对象var1在var2地址上的值var5;
2)比较该地址当前最新的值和我获取到的值var5是否相等,如果相等,则赋值var5+var4;
3)否则重新获取重复1、2。
3.场景举例
假设线程A和线程B同时执行getAndAddInt操作(分别跑在不同CPU上)
1)AtomicInteger中value原始值为3,即主内存中为3,根据JMM模型,线程A和线程B各自持有一份value为3的副本分别在各自的工作内存中。
2)线程A通过getIntVolatile(var1, var2)拿到value的值3,这时线程A被挂起。
3)线程B也通过getIntVolatile(var1, var2)方法获取到value值3,刚好B没有被挂起并执行compareAdeSwapInt方法,比较内存值也为3,成功修改内存值为4,线程B执行完毕。
4)这时线程A恢复,执行compareAndSwapInt方法比较,发现自己工作内存中的value3和主内存中的4不一致,说明该值已经被其他线程抢先一步修改过了,线程A本次修改失败,只能重新读取重新来一遍了。
5)线程A重新获取value值,因为变量value被volatile修饰,所以其他线程对它的修改,线程A总是能够看到,线程A继续执行compareAndSwapInt进行比较替换,直到成功。
4.缺点:
1)CAS的方式相比于锁来说并发性加强了,但如果CAS失败,会一直进行尝试,可能会给CPU带来很大的开销
2)只能保证一个共享变量的原子性,对多个共享变量操作可以用锁来保证原子性。
3)引出ABA问题:不同线程执行时间不均衡导致慢的线程获取到虽然值与期望相等,却中间经历了别的线程更新操作。
posted @ 2019-09-04 17:01  猫不夜行  阅读(636)  评论(0编辑  收藏  举报