源码解读(二):Java中的CAS应用

1、什么是CAS

CAS全称为ConmpareAndSwap:比较和交换;
CAS有三个要素原始值期望值更新值
操作逻辑:将原始值与期望值进行比较,只有在值相同的情况下,将改原始值更新为新值。
CAS是实现多线程同步的原子指令,具有原子性。
原子性:一个操作不能被打断,要么全部执行完毕,要么不执行。
我们可以把它看成一个被加锁的代码块,在这个代码块中CAS执行了两步操作。
1、比较原值和期望值
2、原值更新为新值
与同步代码块不同时,代码块如果获取不到锁会阻塞,而CAS若比较失败,会返回false,不进行更新。

2、CAS在Java中的使用

为了方便介绍,我们以AtomicInteger类为例。

	// value属性在AtomicInteger类的下标位置
	private static final long valueOffset;
    static {
        try {
        	// 获取value属性在AtomicInteger类的下标位置
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }
    public final boolean compareAndSet(int expect, int update) {
    	// java中的CAS方法
    	// this 当前对象,valueOffset 属性下标,expect 期望值,update 更新值
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

我们进入unsafe类看看,一共有三个CAS方法。

	// 
	/**
	 * 对Object类型进行操作
     * 获取var1对象中var2下标位置的属性值(原值),与期望值var4进行对比,若相同原值更新为更新值var5
     * var1 对象
     * var2 对象var1中var2下标位置
     * var4 期望值
     * var5 更新值
     * return true 对比相等,更新成功;false 对比不相等,更新失败。
     */
    public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
	// 对int类型进行操作
    public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
	// 对long类型进行操作
    public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);

这个三个CAS方法看似简简单单,却在JUC起着至关重要的做,并发工具类多处对数据的原子操作都能看见CAS的影子。

3、ABA问题

线程1从内存中取出值为A,线程2也从内存中取出值为A,线程2进行CAS操作值为B,线程2有进行CAS操作值为A,这时线程1进行CAS操作时能够成功。但是我们不能认为这是正确的
就像我们有一百块,拿去吃饭了,后来又挣了一百块,虽然最后还是一百块,可是咱们已经吃饱饭了呀。
为了应对ABA的问题,JAVA中提供了AtomicStampedReference/AtomicMarkableReference,在对象中再加一个标识来记录对象是否发生了改变。
就如同,我们记个账,虽然手里还是一百块,可是账本告诉我们这中间发生了一些故事。

3、ABA问题如何解决

AtomicStampedReference的使用

public static void main(String[] args) {
        AtomicStampedReference asr = new AtomicStampedReference(100, 0);
        asr.compareAndSet(100, 101, asr.getStamp(), asr.getStamp() + 1);
}

在上面的代码我们可以看到,AtomicStampedReference的CAS一共有四个参数,不仅要比较引用的值,还是比较预期的标记(stamp)是否变更过。下面我们看一下AtomicStampedReference的源码。

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);
        }
    }
	// 把reference和stamp封装成Pair对象
    private volatile Pair<V> pair;
    
    /**
     * 对引用和标记进行比较,只有原值同时和期望值相等时,CAS操作才会成功。这里我们不会让stamp出现ABA的情形。
     * 也就避免了引用值ABA调用出现的问题。
     * expectedReference 期望引用值
     * newReference 新引用值
     * expectedStamp 期望标记值
     * newStamp 新标记值
     * return CAS结果,true操作成功,false操作失败
     */
    public boolean compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp) {
        Pair<V> current = pair;
        return
        	//1.原引用值与期望引用值相等进入2,否则返回false
            expectedReference == current.reference &&
            //2.原标记值与期望标记值相等进入3,否则返回false
            expectedStamp == current.stamp &&
            //3.新引用值和新标记值 分别 原引用值和原标记值相等返回true,否则进入4
            ((newReference == current.reference && newStamp == current.stamp) ||
            //4.CAS操作Pair对象成功返回true,否则返回false
             casPair(current, Pair.of(newReference, newStamp)));
    }
    // 获取UNSAFE对象
    private static final sun.misc.Unsafe UNSAFE = sun.misc.Unsafe.getUnsafe();
    // 获取属性pair在AtomicStampedReference类中下标位置
    private static final long pairOffset = objectFieldOffset(UNSAFE, "pair", AtomicStampedReference.class);
	
	// 调用UNSAFE的CAS方法进行CAS操作
    private boolean casPair(Pair<V> cmp, Pair<V> val) {
        return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
    }

	// 获取属性pair在AtomicStampedReference类中下标位置
    static long objectFieldOffset(sun.misc.Unsafe UNSAFE, String field, Class<?> klazz) {
        try {
            return UNSAFE.objectFieldOffset(klazz.getDeclaredField(field));
        } catch (NoSuchFieldException e) {
            // Convert Exception to corresponding Error
            NoSuchFieldError error = new NoSuchFieldError(field);
            error.initCause(e);
            throw error;
        }
    }
}

走读代码我们可以了解到,其实只是对CAS多引入了一个变量,让这个变量专门来记录标记我们对AtomicStampedReference的操作,避免出现了值被改回去了我们无法发现的问题。
AtomicMarkableReference的方式与AtomicStampedReference类似,相比AtomicStampedReference而言,AtomicMarkableReference把标记值由int型换成了boolean,感兴趣的同学可以自行研究一下源码,这里不再赘述。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值