上一篇文章我们介绍了原子类 AtomicInteger 我们知道了该类留下的一个逻辑漏洞–ABA 问题。
ABA 问题的终极解决方案就是 – 版本号。和我们数据库中使用乐观锁加一个 version 字段类似,数据库是每次插入时都检验一下版本号是否一致。而我们解决 ABA 问题的核心也是一样的,修改前检验一下当前版本号是否一致。
JDK 中 AtomicStampedReference/AtomicMarkableReference 这两个类就是使用版本号的方式来解决 AtomicInteger 的 ABA 问题。
两者区别:
类 | AtomicStampedReference | AtomicMarkableReference |
---|---|---|
字段 | stamp | mark |
AtomicStampedReference 中的版本号 stamp 是一个 int 类型
AtomicMarkableReference 中的版本号 mark 则是一个 boolean 类型
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);
}
}
我们再看看相关的构造
/**
* 构造器,初始化时将内部类也初始化
*/
public AtomicStampedReference(V initialRef, int initialStamp) {
pair = Pair.of(initialRef, initialStamp);
}
/**
* 获得当前的对象引用
*/
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;
}
这里讲解一下为什么在 get 方法要使用 int[] 类型的参数。如果对 java 堆栈有一些认识的同学应该很快就懂,如果不知道可以去学习一下 java 基础。
基本类型在栈中执行完毕后就被释放掉了,而使用对象数组的目的可以进行引用传递,将数组第一位的引用指向当前的版本号,这样我们就可以拿到当前版本号了。
核心方法:*
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)));
}
AtomicMarkableReference :
这一个整个类的代码感觉就是复制上面的,改了一个字段名和类型,修改一下方法的名字,其他基本没变化。
private static class Pair<T> {
final T reference;
//AtomicStampedReference 使用的是 int 类型
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);
}
}
AtomicMarkableReference 的好处是只有两种情况,是 true 和 false,感觉上就像是一个标记,int 版本号是根据数值判断,而这个则是根据 true,false 来判断是否被修改过。
总结:
两个类都是为了解决 ABA 问题而出的解决方案,但是具体我也不知道能做些什么。当然,如果是面试的话,我想这两个方案都可以提及即可。