java_虚拟机

Java中的CAS和Unsafe类

2018-09-29  本文已影响44人  对面的你

CAS:Compare and Swap

CAS(V, E, N)

CAS属于乐观锁,多个线程竞争只会有一个胜出,其余线程并不会挂起,而是继续尝试获取更新,当然也可以主动放弃。CAS操作是基于系统原语的(原语的执行必须是连续的,操作期间不会被系统中断,是一条CPU的原子指令),因此是一个不需要加锁的锁,也因此不可能出现死锁的情况。也就是说无锁操作天生免疫死锁。

Unsafe类

java中CAS操作依赖于Unsafe类,Unsafe类所有方法都是native的,直接调用操作系统底层资源执行相应任务,它可以像C一样操作内存指针,是非线程安全的。

Unsafe里的CAS 操作相关

Java中无锁操作CAS基于以下3个方法实现:

//第一个参数o为给定对象,offset为对象内存的偏移量,通过这个偏移量迅速定位字段并设置或获取该字段的值,
//expected表示期望值,x表示要设置的值,下面3个方法都通过CAS原子指令执行操作。
public final native boolean compareAndSwapObject(Object o, long offset,Object expected, Object x);                                                                                                   
public final native boolean compareAndSwapInt(Object o, long offset,int expected,int x); 
public final native boolean compareAndSwapLong(Object o, long offset,long expected,long x);

Atomic系列内部方法是基于下述方法的实现的。

并发包中的原子操作类(Atomic系列)

并发包中的原子操作类提供了许多基于CAS实现的原子操作类。

原子更新基本类型

下面看AtomicInteger类的源码:

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;    
    }   
    //省略一些不常用的方法....
 }

AtomicInteger基本是基于Unsafe类中CAS相关操作实现的,是无锁操作。

public final int getAndIncrement() { 
    return unsafe.getAndAddInt(this, valueOffset, 1);   
}

调用了Unsafe类中的getAndAddInt()方法,该方法执行一个CAS操作,保证线程安全。

//Unsafe类中的getAndAddInt方法
public final int getAndAddInt(Object o, long offset, int delta) {        
    int v;        
    do {            
        v = getIntVolatile(o, offset);        
    } while (!compareAndSwapInt(o, offset, v, v + delta));        
    return v;
}

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

上述源码分析是基于JDK1.8的,如果是1.8之前的方法,1.8之前的方法实现如下:

//JDK 1.7的源码,由for的死循环实现,并且直接在AtomicInteger实现该方法,
//JDK1.8后,该方法实现已移动到Unsafe类中,直接调用getAndAddInt方法即可
public final int incrementAndGet() {    
    for (;;) {        
        int current = get();        
        int next = current + 1;        
        if (compareAndSet(current, next))            
            return next;    
    }
}

CAS操作中可能会带来的ABA问题

假设这样一种场景,当第一个线程执行CAS(V,E,U)操作,在获取到当前变量V,准备修改为新值U前,另外两个线程已连续修改了两次变量V的值,使得该值又恢复为旧值:



无法正确判断这个变量是否已被修改过,一般称这种情况为ABA问题。

ABA问题一般不会有太大影响,产生几率也比较小。但是并不排除极特殊场景下会造成影响,因此需要解决方法:

AtomicStampedReference类

一个带有时间戳的对象引用,每次修改时,不但会设置新的值,还会记录修改时间。在下一次更新时,不但会对比当前值和期望值,还会对比当前时间和期望值对应的修改时间,只有二者都相同,才会做出更新。解决了反复读写时,无法预知值是否已被修改的窘境。

底层实现为:一个键值对Pair存储数据和时间戳,并构造volatile修饰的私有实例;两者都符合预期才会调用Unsafe的compareAndSwapObject方法执行数值和时间戳替换。

AtomicMarkableReference类

一个boolean值的标识,true和false两种切换状态表示是否被修改。不靠谱。

自旋锁

自旋锁:假设在不久的将来,线程可以获得锁,虚拟机会让线程做几个空循环(自旋),若干次循环、尝试获得锁后,如果获得锁,线程进入临界区;如果没有成功,则被挂起。

避免了线程在尝试获得锁失败后,挂起,再次尝试之间,状态不断转换造成的资源浪费,提升效率。

但如果有太多线程进入自旋状态,CPU的占用时间(自选过程)会过长,反而使得效率变低,性能下降。因此,虚拟机限制了自旋次数(几十次至100次),这之后虚拟机会将线程挂起,让出cpu资源。

上一篇 下一篇

猜你喜欢

热点阅读