原子类型:AtomicStampedReference

1 CAS算法ABA问题

AtomicInteger、AtomicBoolean、AtomicLong、AtomicReference这些原子类型,它们无一例外都采用了基于volatile关键字+CAS算法无锁的操作方式来确保共享数据在多线程操作下的线程安全性。

  • volatile关键字保证了线程间的可见性,当某线程操作了被volatile关键字修饰的变量,其他线程可以立即看到该共享变量的变化。
  • CAS算法,即对比交换算法,是由UNSAFE提供的,实质上是通过操作CPU指令来得到保证的。CAS算法提供了一种快速失败的方式,当某线程修改已经被改变的数据时会快速失败。
  • 当CAS算法对共享数据操作失败时,因为有自旋算法的加持,我们对共享数据的更新终究会得到计算。

绝大多数情况下,CAS算法并没有什么问题,但是在需要关心变化值的操作中会存在ABA的问题,比如一个值原来是A,变成了B,后来又变成了A,那么CAS检查时会发现它的值没有发生变化,但是实际上却是发生了变化的。

2 AtomicStampedReference详解

2.1 AtomicStampedReference介绍

如何避免CAS算法带来的ABA问题呢?针对乐观锁在并发情况下的操作,我们通常会增加版本号,比如数据库中关于乐观锁的实现方式,以此来解决并发操作带来的ABA问题。在Java原子包中也提供了这样的实现AtomicStampedReference<E>

AtomicStampedReference在构建的时候需要一个类似于版本号的int类型变量stamped,每一次针对共享数据的变化都会导致该stamped的增加(stamped的自增维护需要应用程序自身去负责,AtomicStampedReference并不提供

AtomicStampedReference的使用也是极其简单的,创建时我们不仅需要指定初始值,还需要设定stamped的初始值,在AtomicStampedReference的内部会将这两个变量封装成Pair对象

// 在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);
    }
}
// volatile 修饰的就是这个pair
private volatile Pair<V> pair;

public AtomicStampedReference(V initialRef, int initialStamp) {
    pair = Pair.of(initialRef, initialStamp);
}

2.2 构造方法

AtomicStampedReference<String> reference =
               new AtomicStampedReference<>("Hello", 1);

2.3 方法

  1. getReference()
// 获取当前引用值,等同于其他原子类型的get方法
getReference()

eg:

AtomicStampedReference<String> reference =
                new AtomicStampedReference<>("Hello", 1);
assert reference.getReference().equals("Hello");
  1. getStamp():获取当前引用值的stamp数值。
AtomicStampedReference<String> reference =
                new AtomicStampedReference<>("Hello", 1);
assert reference.getStamp()==1;
  1. V get(int[] stampHolder):这个方法的意图是获取当前值以及stamp值,但是Java不支持多值的返回,并且在AtomicStampedReference内部Pair被定义为私有的,因此这里就采用了传参的方式来解决(个人觉得这样的方法设计不算优雅,作者如果不想暴露Pair,完全可以再定义一个专门用于返回value和stamp对的public对象)。
/**
返回值就是持有的引用
**/
V get(int[] stampHolder)
AtomicStampedReference<String> reference =
                new AtomicStampedReference<>("Hello", 1);
 // 定义一个数组,最后stamp会被放到这个数组中
int[] holder = new int[1];
String value = reference.get(holder);
assert value.equals("Hello");
assert holder[0] == 1;
  1. compareAndSet(V expectedReference, V newReference,int expectedStamp, int newStamp)
/**
对比并且设置当前的引用值,这与其他的原子类型CAS算法类似,只不过多了expectedStamp和newStamp,只有当expectedReference与当前的Reference相等,且expectedStamp与当前引用值的stamp相等时才会发生设置,否则set动作将会直接失败。
**/
compareAndSet(V expectedReference, V newReference,int expectedStamp, int newStamp);
weakCompareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp);
AtomicStampedReference<String> reference =
                new AtomicStampedReference<>("Hello", 1);
// 更新失败,原因是stamp与期望值不一样
assert !reference.compareAndSet("Hello", "World", 2, 3);
// 更新成功
assert reference.compareAndSet("Hello", "World", 1, 2);
// 验证成功
assert reference.getReference().equals("World");
  1. set(V newReference, int newStamp):设置新的引用值以及stamp。
// 设置新的引用值以及stamp。
void set(V newReference, int newStamp)
  1. attemptStamp(V expectedReference, int newStamp)
// 该方法的主要作用是为当前的引用值设置一个新的stamp,该方法为原子性方法。
attemptStamp(V expectedReference, int newStamp)
AtomicStampedReference<String> reference =
                new AtomicStampedReference<>("Hello", 1);
// 设置成功,但是stamp并未发生变化
assert reference.attemptStamp("Hello", 1);

// 设置失败,原因是期望的引用值不等于当前的引用值
assert !reference.attemptStamp("World", 2);
// stamp并未发生变化
assert reference.getStamp() == 1;
// 设置成功
assert reference.attemptStamp("Hello", 2);
// 验证通过
assert reference.getStamp() == 2;

3 总结

AtomicStampedReference的出现是为了解决CAS算法中的ABA问题,它通过为引用值增加一个stamp戳的方式来避免ABA问题的发生,熟悉数据库开发的朋友肯定知道在多线程或者多系统中,同时对数据库的某条记录进行更改的时候,我们一般是采用乐观锁的方式,即为该记录增加版本号字段,比如如下的更新操作,其实AtomicStampedReference的实现原理也是这样的。

UPDATE TABLE TAB SET X=newValue, VERSION=VERSION+1 WHERE X=oldValue AND VERSION=expectedVersion
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值