AtomicStampedReference和AtomicMarkableReference详解

451 阅读3分钟

AtomicStampedReference和AtomicMarkableReference是解决CAS存在ABA问题的两种方案,他们俩的实现原理大致相同,弄明白AtomicStampedReference的实现原理,AtomicMarkableReference就迎刃而解了。

AtomicStampedReference

AtomicStampedReference维护了一个reference对象和一个int类型的stamp,能够提供原子性更新的操作

一句话总结其中的原理:每次CAS前,先获取当AtomicStampedReference对象里的Pair里的stamp和reference, 该stamp为版本号,reference为期望的对象值,如果获取到的版本号为期望的版本号并且获取到的reference值为期望值,就是说当前线程获取到的版本号在CAS前没有被其他线程修改且reference也没有修改,那么当前线程才能进行CAS操作,如果版本号不是期望值,那么就不执行更新操作。

Pair

Pair是AtomicStampedReference和AtomicMarkableReference类里维护的一个静态内部类

   private static class Pair<T> {
       // 存放的对象,采用泛型
        final T reference;
      // stamp类似于版本号的功能
        final int stamp;
        private Pair(T reference, int stamp) {
            this.reference = reference;
            this.stamp = stamp;
        }
       //提供初始化Pair对象的方法
        static <T> Pair<T> of(T reference, int stamp) {
            return new Pair<T>(reference, stamp);
        }
    }

AtomicMarkableReference的pari类(大致是相同的)

private static class Pair<T> {
    final T reference;
    final boolean mark;
    private Pair(T reference, boolean mark) {
        this.reference = reference;
        this.mark = mark;
    }
    static <T> Pair<T> of(T reference, boolean mark) {
        return new Pair<T>(reference, mark);
    }
}

AtomicStampedReference类和AtomicMarkableReference类里都含有一个volatile修饰的Pair成员变量,它的作用是用来存放当前的Pair值。

private volatile Pair<V> pair; //存放当前的Pair的值

主要还是因为在CAS的时候,需要当前的值和期望的值进行比较,所有需要记录一下

同时获取当前reference值和版本号stamp

给一个数组容量为1的数组给get()方法作为参数,版本号会存放到数组里,返回值为当前的reference值

int[] stampHolder = new int[1];
// 1. 给一个空数组给atomicStampedReference
int value = (int) atomicStampedReference.get(stampHolder);

其实主要就是把当前的pair值给拿出来,然后取出stamp赋值给数组并将reference进行返回即可。

//传入一个长度为1的数组
  public V get(int[] stampHolder) {
     // 获取当前pair对象
        Pair<V> pair = this.pair;
      // 赋值
        stampHolder[0] = pair.stamp;
      //返回当前的对象值
        return pair.reference;
    }

CAS操作

CAS操作源码,有四个值

//四个参数为 期望的对象值,新的对象值,期望的版本号,新的版本号
public boolean compareAndSet(V   expectedReference,
                             V   newReference,
                             int expectedStamp,
                             int newStamp) {
    Pair<V> current = pair;
    return
        //比较期望值
        expectedReference == current.reference &&
        expectedStamp == current.stamp &&
        //如果当前值和新值一样,并且版本号也一样,那就直接return,不做CAS操作
        ((newReference == current.reference &&
          newStamp == current.stamp) ||
         //执行CAS操作
         casPair(current, Pair.of(newReference, newStamp)));
}

casPair()方法的底层就是调用的unsafe提供的compareAndSwapObject方法。

private boolean casPair(Pair<V> cmp, Pair<V> val) {
    return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
}

unsafe

第一步获取到unsafe就能执行CAS操作,获取到unsafe变量

private static final sun.misc.Unsafe UNSAFE = sun.misc.Unsafe.getUnsafe();

第二步获取到共享变量pair的偏移量

 private static final long pairOffset =objectFieldOffset(UNSAFE, "pair", AtomicStampedReference.class);

根据UNSAFE对象和字段field获取到字段在对象里的偏移量, 偏移量可以用sun.misc.Unsafe类里提供一个native方法objectFieldOffset(Field field)方法来获取。

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

AtomicMarkableReference

AtomicMarkableReference与AtomicStampedReference的区别是Pair内部类维护的类型不同。

private static class Pair<T> {
    final T reference;
    final boolean mark;
    private Pair(T reference, boolean mark) {
        this.reference = reference;
        this.mark = mark;
    }
    static <T> Pair<T> of(T reference, boolean mark) {
        return new Pair<T>(reference, mark);
    }
}

采用boolean类型的值替换掉了int stamp, 其他功能的实现方式参考AtomicStampedReference即可。