CAS浅析

一、CAS

  • 定义:

比较和交换(Compare And Swap),它是一条CPU并发原语,是用于多线程同步的原子指令

  • 作用:

判断内存中某个位置的值是否为预期值,如果是则更改为新的值,这个过程是原子性的

  • 思想:

乐观锁的思想(CAS本身不加锁),当多个线程使用CAS操作一个变量,同一时间只有一个能更新成功,其它的失败,但失败的线程不会被挂起,允许再次尝试(自旋)

  • 实现:

CAS操作的执行依赖于Unsafe类的方法

  • 优点:

1)效率高,非阻塞(线程不会挂起,自旋),、

  • 缺点:

1)循环时间长,cpu开销大
2)只能保证一个变量的共享操作
3)ABA问题

  • 扩展-原语:

CAS是一种系统原语,属于操作系统用语范畴,由若干条指令组成,用于完成某个功能,原语的执行必须是连续的,在执行过程中不允许被中断,所以CAS操作是一个原子性操作


二、Unsafe类

  • 作用:
    Unsafe类存在于sun.misc包中,其内部方法操作可以像C的指针一样直接操作内存,Unsafe类中的所有方法都是native修饰的,也就是说Unsafe类中的方法都直接调用操作系统底层资源执行相应任务
  • 1. 源码中相关CAS操作
    在Java中无锁操作CAS基于以下3个方法实现
    public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);

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

    public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
    
  • 2. 线程挂起与恢复
    Java对线程的挂起操作被封装在 LockSupport类中,LockSupport类中有各种版本pack方法,其底层实现最终还是使用Unsafe.park()方法和Unsafe.unpark()方法
//线程调用该方法,线程将一直阻塞直到超时,或者是中断条件出现。  
public native void park(boolean isAbsolute, long time);  
 
//终止挂起的线程,恢复正常
public native void unpark(Object thread); 

三、原子操作类
CAS在Java中的应用,即并发包中的原子操作类(Atomic系列),从JDK 1.5开始提供了java.util.concurrent.atomic包,在该包中提供了许多基于CAS实现的原子操作类

  • 1. 原子更新类
  • AtomicBoolean:原子更新布尔类型
  • AtomicInteger:原子更新整型
  • AtomicLong:原子更新长整型
  • 2. AtomicInteger源码
    AtomicInteger主要是针对int类型的数据执行原子操作,它提供了原子自增方法、原子自减方法以及原子赋值方法等
public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;
 
    // 获取指针类Unsafe
    private static final Unsafe unsafe = Unsafe.getUnsafe();
 
    //下述变量value在AtomicInteger实例对象内的内存偏移量
    private static final long valueOffset;
 
    static {
        try {
           //通过unsafe类的objectFieldOffset()方法,获取value变量在对象内存中的偏移
           //通过该偏移量valueOffset,unsafe类的内部方法可以获取到变量value对其进行取值或赋值操作
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }
   //当前AtomicInteger封装的int变量value
    private volatile int value;
 
    public AtomicInteger(int initialValue) {
        value = initialValue;
    }
    public AtomicInteger() {
    }
   //获取当前最新值,
    public final int get() {
        return value;
    }
    //设置当前值,具备volatile效果,方法用final修饰是为了更进一步的保证线程安全。
    public final void set(int newValue) {
        value = newValue;
    }
    //最终会设置成newValue,使用该方法后可能导致其他线程在之后的一小段时间内可以获取到旧值,有点类似于延迟加载
    public final void lazySet(int newValue) {
        unsafe.putOrderedInt(this, valueOffset, newValue);
    }
   //设置新值并获取旧值,底层调用的是CAS操作即unsafe.compareAndSwapInt()方法
    public final int getAndSet(int newValue) {
        return unsafe.getAndSetInt(this, valueOffset, newValue);
    }
   //如果当前值为expect,则设置为update(当前值指的是value变量)
    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }
    //当前值加1返回旧值,底层CAS操作
    public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }
    //当前值减1,返回旧值,底层CAS操作
    public final int getAndDecrement() {
        return unsafe.getAndAddInt(this, valueOffset, -1);
    }
   //当前值增加delta,返回旧值,底层CAS操作
    public final int getAndAdd(int delta) {
        return unsafe.getAndAddInt(this, valueOffset, delta);
    }
    //当前值加1,返回新值,底层CAS操作
    public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }
    //当前值减1,返回新值,底层CAS操作
    public final int decrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, -1) - 1;
    }
   //当前值增加delta,返回新值,底层CAS操作
    public final int addAndGet(int delta) {
        return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
    }
   //省略一些不常用的方法....
}

  • 3.Unsafe类中 getAndAddInt()方法
    AtomicInteger类中所有自增或自减的方法都间接调用Unsafe类中的getAndAddInt()方法实现了CAS操作,从而保证了线程安全
#var1:AtomicInteger对象本身
#var2:该对象的引用地址
#var4:需要变动的数量
#var5:用var1、var2找出的主内存中的真实值
#用对象当前值和var5进行比较
#如果相同,更新var5+var4并且返回true
#如果不同,继续取值然后再比较,直到更新完成
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;
}

getAndAddInt通过一个while循环不断的重试更新要设置的值,直到成功为止,调用的是Unsafe类中的compareAndSwapInt方法,是一个CAS操作方法

  • 底层原理:
    CAS底层使用JNI调用C代码实现的,如果你有Hotspot源码,那么在Unsafe.cpp 里可以找到它的实现:
static JNINativeMethod methods_15[] = {
    //省略一堆代码...
    {CC"compareAndSwapInt",  CC"("OBJ"J""I""I"")Z",      FN_PTR(Unsafe_CompareAndSwapInt)},
    {CC"compareAndSwapLong", CC"("OBJ"J""J""J"")Z",      FN_PTR(Unsafe_CompareAndSwapLong)},
    //省略一堆代码...
};

我们可以看到compareAndSwapInt实现是在Unsafe_CompareAndSwapInt里面,再深入到Unsafe_CompareAndSwapInt:

UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
  UnsafeWrapper("Unsafe_CompareAndSwapInt");
  oop p = JNIHandles::resolve(obj);
  jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
  return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END

p是取出的对象,addr是p中offset处的地址,最后调用了Atomic::cmpxchg(x, addr, e), 其中参数x是即将更新的值,参数e是原内存的值

inline jint     Atomic::cmpxchg    (jint     exchange_value, volatile jint*     dest, jint     compare_value) {
  int mp = os::is_MP();
  __asm__ volatile (LOCK_IF_MP(%4) "cmpxchgl %1,(%3)"
                    : "=a" (exchange_value)
                    : "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp)
                    : "cc", "memory");
  return exchange_value;
}

四、CAS问题

  • 1.ABA问题
    当第一个线程执行CAS(V,E,U)操作,在获取到当前变量V,准备修改为新值U前,另外两个线程已连续修改了两次变量V的值,使得该值又恢复为旧值,这样的话,我们就无法正确判断这个变量是否已被修改过
    CAS浅析_第1张图片
  • 解决ABA问题
    目前在JDK的atomic包里提供了一个类AtomicStampedReference(带版本号的原子引用类)来解决ABA问题。这个类的compareAndSet方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。

你可能感兴趣的:(Java)