并发编程 — AtomicStampedReference 详解
AtomicInteger、AtomicBoolean、AtomicLong、AtomicReference 这些原子类型,它们无一例外都采用了基于 volatile 关键字 +CAS 算法无锁的操作方式来确保共享数据在多线程操作下的线程安全性。volatile关键字保证了线程间的可见性,当某线程操作了被volatile关键字修饰的变量,其他线程可以立即看到该共享变量的变化。CAS算法,即对比交换
AtomicInteger、AtomicBoolean、AtomicLong、AtomicReference 这些原子类型,它们无一例外都采用了基于 volatile 关键字 +CAS 算法无锁的操作方式来确保共享数据在多线程操作下的线程安全性。
- volatile关键字保证了线程间的可见性,当某线程操作了被volatile关键字修饰的变量,其他线程可以立即看到该共享变量的变化。
- CAS算法,即对比交换算法,是由UNSAFE提供的,实质上是通过操作CPU指令来得到保证的。CAS算法提供了一种快速失败的方式,当某线程修改已经被改变的数据时会快速失败。
- 当CAS算法对共享数据操作失败时,因为有自旋算法的加持,我们对共享数据的更新终究会得到计算。
总之,原子类型用自旋+CAS的无锁操作保证了共享变量的线程安全性和原子性。
绝大多数情况下,CAS算法并没有什么问题,但是在需要关心变化值的操作中会存在 ABA 的问题,比如一个值原来是A,变成了B,后来又变成了A,那么CAS检查时会发现它的值没有发生变化,但是实际上却是发生了变化的。
如何避免CAS算法带来的ABA问题呢?针对乐观锁在并发情况下的操作,我们通常会增加版本号,比如数据库中关于乐观锁的实现方式,以此来解决并发操作带来的ABA问题。在Java原子包中也提供了这样的实现AtomicStampedReference<E>。
1、AtomicStampedReference 详解
AtomicStampedReference在构建的时候需要一个类似于版本号的int类型变量stamped,每一次针对共享数据的变化都会导致该 stamped 的变化(stamped 需要应用程序自身去负责,AtomicStampedReference并不提供,一般使用时间戳作为版本号),因此就可以避免ABA问题的出现,AtomicStampedReference的使用也是极其简单的,创建时我们不仅需要指定初始值,还需要设定stamped的初始值,在AtomicStampedReference的内部会将这两个变量封装成Pair对象,代码如下所示。
静态内部类,封装了 变量引用 和 版本号
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;
/**
*
* @param initialRef 初始变量引用
* @param initialStamp 版本号
*/
public AtomicStampedReference(V initialRef, int initialStamp) {
pair = Pair.of(initialRef, initialStamp);
}
2、常用方法
// 构造函数,初始化引用和版本号
public AtomicStampedReference(V initialRef, int initialStamp)
// 以原子方式获取当前引用值
public V getReference()
// 以原子方式获取当前版本号
public int getStamp()
// 以原子方式获取当前引用值和版本号
public V get(int[] stampHolder)
// 以原子的方式同时更新引用值和版本号
// 当期望引用值不等于当前引用值时,操作失败,返回false
// 当期望版本号不等于当前版本号时,操作失败,返回false
// 在期望引用值和期望版本号同时等于当前值的前提下
// 当新的引用值和新的版本号同时等于当前值时,不更新,直接返回true
// 当新的引用值和新的版本号不同时等于当前值时,同时设置新的引用值和新的版本号,返回true
public boolean weakCompareAndSet(V expectedReference,
V newReference,
int expectedStamp,
int newStamp)
// 以原子的方式同时更新引用值和版本号
// 当期望引用值不等于当前引用值时,操作失败,返回false
// 当期望版本号不等于当前版本号时,操作失败,返回false
// 在期望引用值和期望版本号同时等于当前值的前提下
// 当新的引用值和新的版本号同时等于当前值时,不更新,直接返回true
// 当新的引用值和新的版本号不同时等于当前值时,同时设置新的引用值和新的版本号,返回true
public boolean compareAndSet(V expectedReference,
V newReference,
int expectedStamp,
int newStamp)
// 以原子方式设置引用的当前值为新值newReference
// 同时,以原子方式设置版本号的当前值为新值newStamp
// 新引用值和新版本号只要有一个跟当前值不一样,就进行更新
public void set(V newReference, int newStamp)
// 以原子方式设置版本号为新的值
// 前提:引用值保持不变
// 当期望的引用值与当前引用值不相同时,操作失败,返回fasle
// 当期望的引用值与当前引用值相同时,操作成功,返回true
public boolean attemptStamp(V expectedReference, int newStamp)
// 使用`sun.misc.Unsafe`类原子地交换两个对象
private boolean casPair(Pair<V> cmp, Pair<V> val)
更多推荐
所有评论(0)