CAS, Unsafe和原子类详解

268 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第6天,点击查看活动详情

CAS

什么是CAS

cas全称Compare-And-Swap,直译对比交换。是一条CPU的原子指令,其方法是让CPU必将两个值是否相等,然后原子的更新某个位置的值,实现方式是靠硬件实现的。

cas操作是原子性的,所以多线程并发使用cas更新数据时,可以不使用锁。

cas使用示例

在java中,封装了Atomic*开头的类。

举一个简单的例子 AtomicInteger

public class CasDemo {
    private AtomicInteger i = new AtomicInteger(0);
    public int add(){
        return i.addAndGet(1);
    }
}

如果不使用cas进行操作的话,为了保证多线程中不会出现并发问题,就需要进行加锁处理

public class CasDemo {
    private int i=0;
    public synchronized int add(){
        return i++;
    }
}

cas问题

ABA问题

因为cas在操作值的时候,会先检查值是否发生变化,如果没有发生变化,则将本次修改的值更新,如果有变化,会获取最近的值在进行比较,从而更新值,这样就会产生一个问题,假设第一个A线程把数值a从1更新到了2,但是此时另一个线程进来了,A线程并没有结束,B线程获取到最新的值为a=2,但是B线程把a又改为1,因为更新完的值对所有线程都具有可见性,所以A线程在读取的时候又会读到a=1,此时就产生了ABA问题。

解决办法:在jdk1.5之后,在Atomic包中引入了一个AtomicStampedReference来解决ABA问题

循环时间长开销大

当cas比较两个值不成功时,会陷入到循环取值比较,但是如果长时间自旋不成功的时候,会给cpu带来非常大的执行开销,甚至可能进入一个无限循环

解决方案:jdk1.8之后引入的LongAddr来解决

只能保证一个共享变量的原子操作

当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁。

解决方案:jdk1.5之后提供了AtomicReference类来保证引用对象之间的原子性,就可以把多个变量放在一个对象里来进行CAS操作

UnSafe

Unsafe是位于sun.misc包下的一个类,主要提供一些用于执行低级别、不安全操作的方法,如直接访问系统内存资源、自主管理内存资源等,这些方法在提升Java运行效率、增强Java语言底层资源操作能力方面起到了很大的作用。

java中Atomic*类中的底层主要就是通过unsafe操作的

AtomicInteger

原子类以AtomicInteger为例

常用api如下

public final int get():获取当前的值
public final int getAndSet(int newValue):获取当前的值,并设置新的值
public final int getAndIncrement():获取当前的值,并自增
public final int getAndDecrement():获取当前的值,并自减
public final int getAndAdd(int delta):获取当前的值,并加上预期的值

源码

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 {
            //用于获取value字段相对当前对象的“起始地址”的偏移量
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }
​
    private volatile int value;
​
    /**
     * Creates a new AtomicInteger with the given initial value.
     *
     * @param initialValue the initial value
     */
    public AtomicInteger(int initialValue) {
        value = initialValue;
    }
​
    /**
     * Creates a new AtomicInteger with initial value {@code 0}.
     */
    public AtomicInteger() {
    }
​
    /**
     * Gets the current value.
     *
     * @return the current value
     */
    //返回当前值
    public final int get() {
        return value;
    }
​
    /**
     * Sets to the given value.
     *
     * @param newValue the new value
     */
    public final void set(int newValue) {
        value = newValue;
    }
    ......
}

AtomicInteger 底层用的是volatile的变量和CAS来进行更改数据的。

而value使用volatile进行修饰的,这样的话,就可以保证多线程之间的可见性

AtomicStampedReference

由于一般的Atomic*会产生ABA问题,所以jdk1.5之后,在Atomic包中引入了一个AtomicStampedReference来解决ABA问题,来看源码

public class AtomicStampedReference<V> {
    private static class Pair<T> {
        final T reference;  //维护对象引用
        final int stamp;  //用于标志版本
        private Pair(T reference, int stamp) {
            this.reference = reference;
            this.stamp = stamp;
        }
        static <T> Pair<T> of(T reference, int stamp) {
            return new Pair<T>(reference, stamp);
        }
    }
    private volatile Pair<V> pair;
    ....
    
    /**
      * expectedReference :更新之前的原始值
      * newReference : 将要更新的新值
      * expectedStamp : 期待更新的标志版本
      * newStamp : 将要更新的标志版本
      */
    public boolean compareAndSet(V   expectedReference,
                             V   newReference,
                             int expectedStamp,
                             int newStamp) {
        // 获取当前的(元素值,版本号)对
        Pair<V> current = pair;
        return
            // 引用没变
            expectedReference == current.reference &&
            // 版本号没变
            expectedStamp == current.stamp &&
            // 新引用等于旧引用
            ((newReference == current.reference &&
            // 新版本号等于旧版本号
            newStamp == current.stamp) ||
            // 构造新的Pair对象并CAS更新
            casPair(current, Pair.of(newReference, newStamp)));
    }
​
    private boolean casPair(Pair<V> cmp, Pair<V> val) {
        // 调用Unsafe的compareAndSwapObject()方法CAS更新pair的引用为新引用
        return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
    }

防止ABA问题的产生,在AtomicStampedReference类中添加了一个版本号的概念,会判断如果元素值和版本号都没有变化,并且和新的也相同,返回true,如果元素值和版本号都没有变化,并且和新的不完全相同,就构造一个新的Pair对象并执行CAS更新pair。