zagnix 2020-04-16
在原子类持续累加或累减时,比如AtomicInteger的incrementAndGet时,是不存在ABA问题的,但compareAndSet或者updateAndGet是可能存在ABA问题,像AtomicBoolean或AtomicLong等这样的变量在多线程修改时,也都存在ABA的问题。
为了理解ABA,下面的例子,针对线程1来说,第一次的A也就是1和第二次的A是另外修改过的1,实际上并不是同一个A(1)。
public static void main(String[] args) { AtomicInteger atomicInteger = new AtomicInteger(1); new Thread(()->{ int value = atomicInteger.get(); System.out.println("thread 1 read value: " + value); // 阻塞1s LockSupport.parkNanos(1000000000L); if (atomicInteger.compareAndSet(value, 3)) { System.out.println("thread 1 update from " + value + " to 3"); } else { System.out.println("thread 1 update fail!"); } }).start(); new Thread(()->{ int value = atomicInteger.get(); System.out.println("thread 2 read value: " + value); if (atomicInteger.compareAndSet(value, 2)) { System.out.println("thread 2 update from " + value + " to 2"); // do sth value = atomicInteger.get(); System.out.println("thread 2 read value: " + value); if (atomicInteger.compareAndSet(value, 1)) { System.out.println("thread 2 update from " + value + " to 1"); } } }).start(); }
为了解决这个问题,引入了AtomicStampedReference。
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; private static final sun.misc.Unsafe UNSAFE = sun.misc.Unsafe.getUnsafe(); private static final long pairOffset = objectFieldOffset(UNSAFE, "pair", AtomicStampedReference.class);
public AtomicStampedReference(V initialRef, int initialStamp) { pair = Pair.of(initialRef, initialStamp); }
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的问题的版本:
private static void testStamp() { AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1, 1); new Thread(()->{ int[] stampHolder = new int[1]; int value = atomicStampedReference.get(stampHolder); int stamp = stampHolder[0]; System.out.println("thread 1 read value: " + value + ", stamp: " + stamp); // 阻塞1s LockSupport.parkNanos(1000000000L); if (atomicStampedReference.compareAndSet(value, 3, stamp, stamp + 1)) { System.out.println("thread 1 update from " + value + " to 3"); } else { System.out.println("thread 1 update fail!"); } }).start(); new Thread(()->{ int[] stampHolder = new int[1]; int value = atomicStampedReference.get(stampHolder); int stamp = stampHolder[0]; System.out.println("thread 2 read value: " + value + ", stamp: " + stamp); if (atomicStampedReference.compareAndSet(value, 2, stamp, stamp + 1)) { System.out.println("thread 2 update from " + value + " to 2"); // do sth value = atomicStampedReference.get(stampHolder); stamp = stampHolder[0]; System.out.println("thread 2 read value: " + value + ", stamp: " + stamp); if (atomicStampedReference.compareAndSet(value, 1, stamp, stamp + 1)) { System.out.println("thread 2 update from " + value + " to 1"); } } }).start(); }
<add key="RedisPath" value="127.0.0.1:6379"/> todo:这里配置自己redis的ip地址和端口号。//ReadServerList:可读的Redis链接地