当前位置:网站首页>并发编程学习笔记 之 原子操作类AtomicReference、AtomicStampedReference详解
并发编程学习笔记 之 原子操作类AtomicReference、AtomicStampedReference详解
2022-07-29 05:20:00 【姠惢荇者】
一、前言
在《并发编程学习笔记 之 原子操作类AtomicInteger详解》中,我们学习了原子操作类AtomicInteger的用法,类似的还有AtomicLong、AtomicBoolean等类型,这些都是针对基本类型定义的原子性操作,那么针对对象是否有原子性操作呢?这一节,我们就开始学习原子操作类AtomicReference,看看如何实现针对对象的原子性操作。
二、AtomicReference的直观体验
这里我们改写了《Java高并发编程详解:深入理解并发核心库》一书中的例子,设计一个个人银行账号资金变化的场景:
- 个人账号被设计为不可变对象,一旦创建就无法进行修改。
- 个人账号类只包含两个字段:账号名、现金数字。
- 为了便于验证,我们约定个人账号的现金只能增多而不能减少。
首先,我们设计一个个人银行账号类,包括账号名、现金数字两个字段,如下所示:
@Data
@AllArgsConstructor
public class DebitCard {
private final String account; //账号名
private final int amount; //现金数字
}
第一种场景: 使用volatile 关键字修饰DebitCard 对象,然后模拟多用户(多线程)同时转钱到账户,实现如下:
public class AtomicReferenceTest {
private static final int THREADS_CONUT = 20;
static volatile DebitCard debitCard = new DebitCard("Alex",0);
public static void increase() {
final DebitCard dc = debitCard;
DebitCard newDc = new DebitCard(dc.getAccount(),dc.getAmount()+10);
debitCard = newDc;
}
public static void main(String[] args) {
for(int i = 0; i < THREADS_CONUT; i++){
Thread t = new Thread(new Runnable() {
@Override
public void run() {
try {
//为了避免线程快速执行完成,变成了类似串行执行的效果
TimeUnit.SECONDS.sleep(new Random().nextInt(5));
} catch (InterruptedException e) {
e.printStackTrace();
}
increase();
}
});
t.start();
}
while (Thread.activeCount() > 2) {
Thread.yield();
}
System.out.println("debitCard:" + debitCard.getAmount());
}
}
多次运行上述代码,会发现,无法保证每次结果都是预期的结果,原因在上一节讲解原子操作类AtomicInteger时,已经分析了。如果采用想保证操作的原子性,我们需要借助synchronized关键字实现,修改increase方法即可:
public synchronized static void increase() {
final DebitCard dc = debitCard;
DebitCard newDc = new DebitCard(dc.getAccount(),dc.getAmount()+10);
debitCard = newDc;
}
第二种场景: 借助我们这节的主角原子操作类AtomicReference实现非阻塞的原子操作,本质还是基于CAS算法实现,具体实现如下:
public class AtomicReferenceTest {
private static final int THREADS_CONUT = 20;
private static AtomicReference<DebitCard> ref = new AtomicReference<>(new DebitCard("Alex",0));
public static void increase2() {
//每一次对AtomicReference的更新操作,我们都采用CAS这一乐观非阻塞的方式进行,
// 因此也会存在对DebitCard对象引用更改失败的问题
DebitCard dc = null;
DebitCard newDC = null;
do{
// 获取AtomicReference的当前值
dc = ref.get();
// 基于AtomicReference的当前值创建一个新的DebitCard
newDC = new DebitCard(dc.getAccount(), dc.getAmount() + 10);
// 基于CAS算法更新AtomicReference的当前值
}while (!ref.compareAndSet(dc, newDC));
}
public static void main(String[] args) {
for(int i = 0; i < THREADS_CONUT; i++){
Thread t = new Thread(new Runnable() {
@Override
public void run() {
try {
//为了避免线程快速执行完成,变成了类似串行执行的效果
TimeUnit.SECONDS.sleep(new Random().nextInt(5));
} catch (InterruptedException e) {
e.printStackTrace();
}
increase2();
}
});
t.start();
}
while (Thread.activeCount() > 2) {
Thread.yield();
}
System.out.println("debitCard:" + ref.get().getAmount());
}
}
通过CAS算法实现了原子性操作。
三、AtomicReference的常见用法
1、构造函数
和AtomicInteger构造参数类似,包括无参和有参构造函数如下:
- AtomicReference():当使用无参构造函数创建AtomicReference对象的时候,需要再次调用set()方法为AtomicReference内部的value指定初始值。
- AtomicReference(V initialValue):创建AtomicReference对象时顺便指定初始值。
2、compareAndSet()方法
- compareAndSet(V expect, V update):原子性地更新AtomicReference内部的value值,其中expect代表当前AtomicReference的value值,update则是需要设置的新引用值。该方法会返回一个boolean的结果,当expect和AtomicReference的当前值不相等时,修改会失败,返回值为false,若修改成功则会返回true。
其他方法,和AtomicInteger类中的类似,这里不再一一列举,详细的可以参看源码。
关于compareAndSet()方法的实现,和AtomicInteger中的方法类似,也是借助unsafe对象的compareAndSwapObject()方法实现原子性操作。在上述的示例中的第二种场景中,我们利用自旋+CAS算法,实现了increase2()方法的原子性操作。
四、CAS算法ABA问题
在上述场景中,我们假设了个人账号的现金只能增多而不能减少,在现实生活中,个人账户的现金肯定是有增有减的,那么如果是这种情况下,上述实现会有什么问题呢?这就是CAS算法的ABA问题。
CAS算法ABA问题描述如下:如果有T1、T2两个线程,当T1读取内存变量为A,T2修改内存变量为B,T2修改内存变量为A,这时T1再CAS操作A时是可行的。但实际上在T1第二次操作A时,已经被其他线程修改过了。
对于ABA问题,比较有效的方案是引入版本号,内存中的值每发生一次变化,版本号都+1;在进行CAS操作时,不仅比较内存中的值,也会比较版本号,只有当二者都没有变化时,CAS才能执行成功。Java语言中的AtomicStampedReference类便是使用版本号来解决ABA问题的。
五、AtomicStampedReference
AtomicStampedReference在构建的时候需要一个类似于版本号的int类型变量stamped,每一次针对共享数据的变化都会导致该stamped的增加(stamped的自增维护需要应用程序自身去负责,AtomicStampedReference并不提供),因此就可以避免ABA问题的出现,AtomicStampedReference的使用也是极其简单的,创建时我们不仅需要指定初始值,还需要设定stamped的初始值,在AtomicStampedReference的内部会将这两个变量封装成Pair对象。
- AtomicStampedReference构造函数,在创建AtomicStampedReference时除了指定引用值的初始值之外还要给定初始的stamp。
public AtomicStampedReference(V initialRef, int initialStamp) {
pair = Pair.of(initialRef, initialStamp);
}
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);
}
}
- getReference():获取当前引用值,等同于其他原子类型的get方法。
public V getReference() {
return pair.reference;
}
- getStamp():获取当前引用值的stamp数值。
public int getStamp() {
return pair.stamp;
}
- V get(int[] stampHolder):这个方法的意图是获取当前值以及stamp值,但是Java不支持多值的返回,并且在AtomicStampedReference内部Pair被定义为私有的,因此这里就采用了传参的方式来解决,其中stampHolder参数用于获取stamp,返回值为reference。这个设计有点儿奇怪。
public V get(int[] stampHolder) {
Pair<V> pair = this.pair;
stampHolder[0] = pair.stamp;
return pair.reference;
}
- compareAndSet(V expectedReference, V newReference,int expectedStamp, int newStamp):对比并且设置当前的引用值,这与其他的原子类型CAS算法类似,只不过多了expectedStamp和newStamp,只有当expectedReference与当前的Reference相等,且expectedStamp与当前引用值的stamp相等时才会发生设置,否则set动作将会直接失败。weakCompareAndSet()方法,和该方法用法完全一样。
- set(V newReference, int newStamp):设置新的引用值以及stamp。
- attemptStamp(V expectedReference, int newStamp):该方法的主要作用是为当前的引用值设置一个新的stamp,该方法为原子性方法。
边栏推荐
- The Platonic metauniverse advocated by musk has long been verified by platofarm
- Extreme deflation and perpetual motion machine model will promote the outbreak of platofarm
- Laravel swagger add access password
- 裸金属云FASS高性能弹性块存储解决方案
- How to make interesting apps for deep learning with zero code (suitable for novices)
- Use of file upload (2) -- upload to Alibaba cloud OSS file server
- Performance comparison | FASS iSCSI vs nvme/tcp
- Flink connector Oracle CDC 实时同步数据到MySQL(Oracle19c)
- Process management of day02 operation
- 重庆大道云行作为软件产业代表受邀参加渝中区重点项目签约仪式
猜你喜欢

Simple optimization of interesting apps for deep learning (suitable for novices)
![30 knowledge points that must be mastered in quantitative development [what is individual data]?](/img/13/9e5e44b371d79136e30dd86927ff3a.png)
30 knowledge points that must be mastered in quantitative development [what is individual data]?

C# 连接 SharepointOnline WebService

Starfish OS: create a new paradigm of the meta universe with reality as the link

XDFS&中国日报社在线协同编辑平台典型案例

Intelligent security of the fifth space ⼤ real competition problem ----------- PNG diagram ⽚ converter

如何零代码制作深度学习的趣味app(适合新手)

机器学习让文字识别更简单:Kotlin+MVVM+华为ML Kit

C# 判断用户是手机访问还是电脑访问

Okaleido Tiger 7.27日登录Binance NFT,首轮已获不俗成绩
随机推荐
“山东大学移动互联网开发技术教学网站建设”项目实训日志六
NIFI 改UTC时间为CST时间
Intelligent security of the fifth space ⼤ real competition problem ----------- PNG diagram ⽚ converter
Huawei 2020 school recruitment written test programming questions read this article is enough (Part 2)
如何零代码制作深度学习的趣味app(适合新手)
win10+opencv3.2+vs2015配置
Laravel服务容器(上下文绑定的运用)
Under the bear market of encrypted assets, platofarm's strategy can still obtain stable income
Elastic box flex
Okaleido tiger logged into binance NFT on July 27, and has achieved good results in the first round
Thinkphp6 output QR code image format to solve the conflict with debug
Mobile terminal -flex item attribute
Flink connector Oracle CDC 实时同步数据到MySQL(Oracle19c)
How to make interesting apps for deep learning with zero code (suitable for novices)
与张小姐的春夏秋冬(4)
加密资产熊市之下,PlatoFarm的策略玩法依旧能获得稳定收益
【数据库】数据库课程设计一一疫苗接种数据库
焕然一新,swagger UI 主题更改
ssm整合
Power BI Report Server 自定义身份验证