当前位置:网站首页>并发编程学习笔记 之 原子操作类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,该方法为原子性方法。
边栏推荐
- 全闪分布式,如何深度性能POC?
- How does PHP generate QR code?
- 重庆大道云行作为软件产业代表受邀参加渝中区重点项目签约仪式
- Training log III of "Shandong University mobile Internet development technology teaching website construction" project
- Study and research the way of programming
- MySQL decompressed version windows installation
- Fantom (FTM) surged 45% before the FOMC meeting
- 闪贷Dapp的调研及实现
- Dao race track is booming. What are the advantages of m-dao?
- D3.JS 纵向关系图(加箭头,连接线文字描述)
猜你喜欢

CMD window under Windows connects to MySQL and operates the table

Refresh, swagger UI theme changes

山寨币SHIB 在 ETH 鲸鱼的投资组合中拥有 5.486 亿美元的股份——交易者应提防……

Fantom (FTM) surged 45% before the FOMC meeting

Go|Gin 快速使用Swagger

Training log 6 of the project "construction of Shandong University mobile Internet development technology teaching website"

iSCSI vs iSER vs NVMe-TCP vs NVMe-RDMA

Super simple integration HMS ml kit face detection to achieve cute stickers

“山东大学移动互联网开发技术教学网站建设”项目实训日志四

xSAN高可用—XDFS与SAN融合焕发新生命力
随机推荐
Move protocol global health declaration, carry out the health campaign to the end
『全闪实测』数据库加速解决方案
中海油集团,桌面云&网盘存储系统应用案例
Mobile terminal -flex item attribute
麦当娜“Hellbent”购买130万美元的nft无聊猿,现在被认为太贵了
Laravel service container (inheritance and events)
Sports health is deeply rooted in the hearts of the people, and move protocol leads quality life
XDFS&中国日报社在线协同编辑平台典型案例
Process management of day02 operation
"Shandong University mobile Internet development technology teaching website construction" project training log I
打印出1-100之间的所有质数
改哭了,终于解决了Cannot read properties of undefined (reading ‘parseComponent‘)
加密资产熊市之下,PlatoFarm的策略玩法依旧能获得稳定收益
深度学习的趣味app简单优化(适合新手)
Elastic box flex
钉钉告警脚本
Synchronous development with open source projects & codereview & pull request & Fork how to pull the original warehouse
XDFS&空天院HPC集群典型案例
C# 连接 SharepointOnline WebService
MySql统计函数COUNT详解