前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >理解ABA问题_什么叫ABA

理解ABA问题_什么叫ABA

作者头像
全栈程序员站长
发布2022-11-17 13:42:05
4550
发布2022-11-17 13:42:05
举报

大家好,又见面了,我是你们的朋友全栈君。

理解ABA问题

1 ABA问题的产生

所谓ABA问题,就是比较并交换的循环,存在一个时间差,而这个时间差可能带来意想不到的问题。比如线程1和线程2同时也从内存取出A,线程T1将值从A改为B,然后又从B改为A。线程T2看到的最终值还是A,经过与预估值的比较,二者相等,可以更新,此时尽管线程T2的CAS操作成功,但不代表就没有问题。

有的需求,比如CAS,只注重头和尾的一致,只要首尾一致就接受。但是有的需求,还看重过程,中间不能发生任何修改,这就引出了AtomicReference原子引用

2 原子引用 AtomicReference

AtomicInteger对整数进行原子操作,AtomicInteger对长整型数进行原子操作,AtomicBoolean对布尔型数进行原子操作,但实际上这些是完全不够的,如果是一个POJO呢?可以用AtomicReference来包装这个POJO,使其操作原子化

Class AtomicReference < V >,Value就是我们需要进行原子包装的泛型类。

示例:

代码语言:javascript
复制
@Getter
@ToString
@AllArgsConstructor
class User { 

String userName;
int age;
}
public class AtomicRefrenceDemo { 

public static void main(String[] args) { 

User z3 = new User("张三", 22);
User l4 = new User("李四", 23);
AtomicReference<User> atomicReference = new AtomicReference<>();
atomicReference.set(z3);
System.out.println(atomicReference.compareAndSet(z3, l4) + "\t" + atomicReference.get().toString());
System.out.println(atomicReference.compareAndSet(z3, l4) + "\t" + atomicReference.get().toString());
}
}

Jetbrains全家桶1年46,售后保障稳定

输出结果:

true User(userName=李四, age=23) false User(userName=李四, age=23)

那么我们如何在原子引用的基础上,解决ABA问题呢,请看带时间戳的原子引用 AtomicStampedReference。


3 带时间戳的原子引用 AtomicStampedReference 解决ABA问题

使用AtomicStampedReference类可以解决ABA问题。这个类维护了一个“版本号”Stamp“,在进行CAS操作的时候,不仅要比较当前值,还要比较版本号。只有两者都相等,才执行更新操作。

核心方法:

代码语言:javascript
复制
static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(initialRef, initialStamp);
int stamp = atomicStampedReference.getStamp()
代码语言:javascript
复制
AtomicStampedReference.compareAndSet(expectedReference,newReference,oldStamp,newStamp);

示例:

代码语言:javascript
复制
public class ABADemo { 

static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);
static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);
public static void main(String[] args) { 

System.out.println("=====以下时ABA问题的产生=====");
new Thread(() -> { 

atomicReference.compareAndSet(100, 101);
atomicReference.compareAndSet(101, 100);
}, "Thread 1").start();
new Thread(() -> { 

try { 

//保证线程1完成一次ABA操作
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) { 

e.printStackTrace();
}
System.out.println(atomicReference.compareAndSet(100, 2019) + "\t" + atomicReference.get());
}, "Thread 2").start();
try { 

TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) { 

e.printStackTrace();
}
System.out.println("=====以下时ABA问题的解决=====");
new Thread(() -> { 

int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "\t第1次版本号" + stamp);
try { 

TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) { 

e.printStackTrace();
}
atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
System.out.println(Thread.currentThread().getName() + "\t第2次版本号" + atomicStampedReference.getStamp());
atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
System.out.println(Thread.currentThread().getName() + "\t第3次版本号" + atomicStampedReference.getStamp());
}, "Thread 3").start();
new Thread(() -> { 

int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "\t第1次版本号" + stamp);
try { 

TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) { 

e.printStackTrace();
}
boolean result = atomicStampedReference.compareAndSet(100, 2019, stamp, stamp + 1);
System.out.println(Thread.currentThread().getName() + "\t修改是否成功" + result + "\t当前最新实际版本号:" + atomicStampedReference.getStamp());
System.out.println(Thread.currentThread().getName() + "\t当前最新实际值:" + atomicStampedReference.getReference());
}, "Thread 4").start();
}
}

输出结果:

===== 以下时ABA问题的产生 ===== true 2019 ===== 以下时ABA问题的解决 ===== Thread 3 第1次版本号1 //初始版本号 Thread 4 第1次版本号1 //初始版本号 Thread 3 第2次版本号2 //第一次修改后的版本号 Thread 3 第3次版本号3 //第二次修改后的版本号 Thread 4 修改是否成功false 当前最新实际版本号:3 //修改失败,此时T4的版本号为1+1,但实际T3已经将版本号增加到了3,T4修改失败 Thread 4 当前最新实际值:100

代码语言:javascript
复制
			try { 

TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) { 

e.printStackTrace();
}

T3线程拿到第一次版本号后睡眠2秒,保证T4线程能拿到和它一样的初始版本号。

代码语言:javascript
复制
            try { 

TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) { 

e.printStackTrace();
}

T4线程拿到第一次版本号后再睡眠4秒,保证在此期间T3线程已经完成了一次ABA操作。

代码语言:javascript
复制
atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);

第一个参数代表预估值,第二个参数代表更新值,第三个参数代表预估版本号,第四个参数代表更新版本号。 如果预估值与内存实际值相等,预估版本号与实际版本号相等,则更新内存值为更新值,更新版本号为更新版本号。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/234260.html原文链接:https://javaforall.cn

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022年11月3日 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 理解ABA问题
  • 1 ABA问题的产生
  • 2 原子引用 AtomicReference
  • 3 带时间戳的原子引用 AtomicStampedReference 解决ABA问题
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档