前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >jdk源码分析之AtomicStampedReference--原子变量ABA问题解决方案

jdk源码分析之AtomicStampedReference--原子变量ABA问题解决方案

作者头像
叔牙
发布2020-11-19 14:54:28
4360
发布2020-11-19 14:54:28
举报
文章被收录于专栏:一个执拗的后端搬砖工

上一篇讲到,jdk1.5引入了并发包,java.util.concurrent.atomic包下有很多原子变量的操作类,但是基本都存在一个问题,ABA问题,也就是并发场景下原子变量被修改了,然后又被改回来,线程再做CAS计算的时候无法感知当前的原子变量的内容已经不是当初的内容了,只是单纯的比较值是否相等无法满足一些特定的业务场景。当然Doug Lea大神也考虑到了这个问题,于是就引入了AtomicStampedReference和AtomicMarkableReference,接下来就AtomicStampedReference的原理和用法做一下详细的介绍。

首先还是先看一下AtomicStampedReference的声明和代码结构:

类声明中定义了一个泛型V,也就是AtomicStampedReference包裹的具体内容,然后接着定义了一个私有静态内部类Pair,同样有声明泛型,有两个成员变量,reference是引用的值,stamp是版本号也可以理解为时间戳(尽量满足单向递增),Pair类的意思就是存储一个值和版本号对应的关系,便于修改的时候作比较。然后可以看到一个成员变量pair,和唯一的构造器,构造器必须有两个入参,对应pair中的两个变量。

然后接着看代码,我们找到了如下一段代码:

// Unsafe mechanics

private static final sun.misc.Unsafe UNSAFE = sun.misc.Unsafe.getUnsafe();

private static final long pairOffset =

objectFieldOffset(UNSAFE, "pair", AtomicStampedReference.class);

和上一篇相似,也是声明内存操作变量UNSAFE和获取成员变量pair的相对位置。

然后我们找到一个更新操作的方法compareAndSet,没猜错的话能够解决原子变量ABA问题的就是它了,看一下代码实现:

/**

* Atomically sets the value of both the reference and stamp

* to the given update values if the

* current reference is {@code ==} to the expected reference

* and the current stamp is equal to the expected stamp.

*

* @param expectedReference the expected value of the reference

* @param newReference the new value for the reference

* @param expectedStamp the expected value of the stamp

* @param newStamp the new value for the stamp

* @return true if successful

*/

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)));

}

从方法注释中我们看到,使用新的值和版本号原子性的修改原来的值和版本号。方法入参expectedReference表示期望内存中的值,newReference表示想修改成的值,期望当前值对应的版本号,newStamp表示修改成功后版本号改为的值;然后获当前对象中的变量pair,返回是否修改成功;expectedReference == current.reference表示要满足我拿到的值和pair中的值一致(未被其他线程修改成其他值),expectedStamp == current.stamp表示同时也要满足拿到的版本号和pair中的版本号一致,(newReference == current.reference && newStamp == current.stamp)表示要修改成的值和要修改成的版本号和当前pair中的值和版本号一致,也就是说没有做任何修改,返回成功,|| casPair(current, Pair.of(newReference, newStamp)) 表示尝试修改成新的值和版本号并返回操作结果;看下casPair的实现:

private boolean casPair(Pair<V> cmp, Pair<V> val) {

return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);

}

可以看到casPair是调用原生的CAS方法尝试将当前对象相对位置偏移量为pairOffset的值从cmp改成val,如果成功返回true,否则返回false。

接下来我们根据案例来分析其使用方式来更好的加深对AtomicStampedReference的理解:

我们新建了一个原子引用,初始值是10,初始版本号是0,然后尝试把引用的值从10改成11,将版本号从0改成1,根据打印结果可以看到修改成功,并且修改后的值和版本号符合我们的预期;然后我们把上述代码做一下修改:

把asr.compareAndSet第一个参数改成了9,根据打印结果我可以看到修改失败,并且值没有被修改。为什么修改失败,因为入参9表示期望当前的值,而实际是10,程序认为pair中的值已经被其他线程修改,此次修改失败。

然后将上述程序再做一下修改如下:

把期望的版本号从0改成了2,运行程序发现修改失败且内容未被修改。为什么呢?因为虽然期望的值一致,但是版本号不一致,也就是我们开篇所说的ABA问题,值被其他线程从10改成其他值又被改了回来,虽然值看起来没变,但是已经增加了两个版本号,这时候程序认为内容发生了变化,不允许修改。

经过上述的介绍,相信之前对原子变量ABA问题不是很理解的或者对原子变量引用AtomicStampedReference不太了解的,会有一个比较明了的认识和掌握。希望在实际开发中给大家带来帮助和源码层面实现思路的理解。如果有觉得分析不到位或者理解有偏差的,可以直接留言或者私聊我。

原创不易,请多多支持!

附带公众号:

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2017-12-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 PersistentCoder 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档