AtomicStampedReference类源码剖析
为了整理的方便,有的内容我直接转载了这篇文章的内容:AtomicStampedReference源码深度解析_刘Java的博客-CSDN博客
一、AtomicStampedReference类的简介
作为通过原子的方式更新单个引用变量的AtomicReference类的升级版,Atomic包提供了以下2个类:
- AtomicMarkableReference< V >:维护带有
标记位
的对象引用,可以原子方式对其进行更新。
- AtomicStampedReference< V >:维护带有
整数标志
的对象引用,可用原子方式对其进行更新。
上面两个原子类的方法以及原理几乎一致,属于带版本号的原子类
。
1.版本号的由来
我们知道CAS操作的三大问题之一就是“ABA”问题:CAS在操作值的时候,需要检查预期值有没有发生变化,如果没有发生变化则更新。但是,如果一个线程t1首先获取了预期值A,此时另一个线程t2则将值从A变成了B,随后又变成了A,随后t1再使用CAS进行比较交换的时候,会发现它的预期值“没有变化”,但实际上是变化过的。这就是ABA问题的由来。
2.解决ABA问题
ABA问题的解决思路就是使用版本号
,1A->2B->3A
,在Atomic包中,提供了一个现成的AtomicStampedReference
类来解决ABA问题,使用的就是添加版本号的方法。还有一个AtomicMarkableReference实现类,它比AtomicStampedReference更加简单,AtomicStampedReference中每更新一次数据版本号也会更新一次,这样可以使用版本号统计到底更新了多少次,而AtomicMarkableReference仅仅使用了一个boolean值来表示值是否改变过,因此使用的比较少。
二、AtomicStampedReference类的属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 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);
static long objectFieldOffset(sun.misc.Unsafe UNSAFE, String field, Class<?> klazz) { try { return UNSAFE.objectFieldOffset(klazz.getDeclaredField(field)); } catch (NoSuchFieldException e) { NoSuchFieldError error = new NoSuchFieldError(field); error.initCause(e); throw error; } }
|
AtomicStampedReference内部不仅维护了我们的传递的对象reference,还维护了一个int类型的版本号stamp
,它们都被存放到一个Pair类型的内部类实例中。当AtomicStampedReference对应的数据被修改时,除了更新数据本身外,还必须要更新版本号,这个版本号一般都是自增的。当AtomicStampedReference设置对象值时,对象值及版本号都必须满足期望值
,才会更新成功。
1 2 3 4 5 6 7 8 9 10 11
| 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); } }
|
三、AtomicStampedReference类的创建
1 2 3 4
| public AtomicStampedReference(V initialRef, int initialStamp) { pair = Pair.of(initialRef, initialStamp); }
|
问题来了,我们如何拿到AtomicStampedReference类的引用和版本戳两个属性呢,当然是需要依赖get系列的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public V getReference() { return pair.reference; }
public int getStamp() { return pair.stamp; }
public V get(int[] stampHolder) { Pair<V> pair = this.pair; stampHolder[0] = pair.stamp; return pair.reference; }
|
四、AtomicStampedReference类的方法
最重要的就是compareAndSet
方法,它需要传递:期望值、新值、期望版本号、新版本号,当期望值和期望版本号都与此时内部的真实值和真实版本号相等
的时候,就会调用compareAndSwapObject使用一个新的Pair对象替换旧的Pair对象,同时完成reference和stamp的更新。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
|
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) || casPair(current, Pair.of(newReference, newStamp))); }
private boolean casPair(Pair<V> cmp, Pair<V> val) { return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val); }
|
除了compareAndSet
方法外,该类中还有无条件设置新引用和新版本号的set
方法:
1 2 3 4 5 6
| public void set(V newReference, int newStamp) { Pair<V> current = pair; if (newReference != current.reference || newStamp != current.stamp) this.pair = Pair.of(newReference, newStamp); }
|
五、AtomicStampedReference类的测试
实际上,如果更新的数据是无状态的数据,那么使用基本的原子类也可以完成目的,即如果线程A将值从1->2->1,而线程B仅仅是使用了值,这是没什么问题的,但是如果和业务相关联,比较的对象是有状态的,那么可能会出现严重问题。
比如还是线程A将值从1->2->1,而线程B的业务逻辑是如果发现数据改变过,那么就不能操作,这样的话就不能单纯的比较值了,这就需要用到版本号了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| import java.util.concurrent.atomic.AtomicStampedReference; import java.util.concurrent.locks.LockSupport;
public class AtomicStampedReferenceDemo {
public static void main(String args[]) { AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<Integer>(0, 0);
Thread thread = new Thread(() -> { int timestamp = atomicStampedReference.getStamp(); int reference = atomicStampedReference.getReference(); System.out.println("原值reference: " + reference); LockSupport.park(); if (atomicStampedReference.compareAndSet(reference, reference + 1, timestamp, timestamp + 1)) { System.out.println("更新成功,新值reference: " + atomicStampedReference.getReference()); } else { System.out.println("更新失败,新值reference: " + atomicStampedReference.getReference()); System.out.println("虽然原值和新值相等,但是在线程阻塞过程中版本号发生了变化,变化了" + (atomicStampedReference.getStamp() - timestamp) + "次"); } }); thread.start();
Thread thread1 = new Thread(() -> { for (int i = 0; i < 4; i++) { int timestamp = atomicStampedReference.getStamp(); int reference = atomicStampedReference.getReference(); if (i % 2 == 0) { atomicStampedReference.compareAndSet(reference, reference + 1, timestamp, timestamp + 1); } else { atomicStampedReference.compareAndSet(reference, reference - 1, timestamp, timestamp + 1); } } LockSupport.unpark(thread); }); thread1.start(); } }
|