并发编程中的CAS操作:ABA问题及其解决方案
创作时间:
作者:
@小白创作中心
并发编程中的CAS操作:ABA问题及其解决方案
引用
CSDN
1.
https://m.blog.csdn.net/qq_30500575/article/details/145471262
在并发编程中,CAS(Compare and Swap)操作是一种常见的原子操作,用于实现无锁编程。然而,CAS操作也存在一些缺点,其中最典型的就是ABA问题。本文将详细介绍CAS的缺点、ABA问题的产生原因,并提供具体的解决方案。
CAS 的缺点
CAS(Compare and Swap)是一种原子操作,用于实现无锁编程。其基本思想是:比较内存位置的当前值与预期值,如果相等,则将内存位置的值更新为新值。CAS操作通常包含三个参数:内存位置、预期值和新值。
尽管CAS操作在很多场景下都非常有效,但它也存在一些缺点:
- CPU 空转的问题:当多个线程同时尝试更新同一个变量时,可能会导致大量线程在循环中空转,浪费CPU资源。
- 锁 饥饿:如果一个线程长时间持有锁,其他线程可能会长时间无法获得锁,导致锁饥饿问题。
ABA 问题
ABA问题是指在并发编程中,一个变量的值从A变为B,然后再变回A,这种情况下,CAS操作可能会误判为变量值没有发生变化,从而导致错误的结果。
ABA 问题案例
下面通过一个具体的代码示例来说明ABA问题:
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class ABADemo {
// 演示ABA
static AtomicInteger atomicInteger = new AtomicInteger(100);
public static void main(String[] args) {
new Thread(() ->{
atomicInteger.compareAndSet(100,101);
atomicInteger.compareAndSet(101,100);
},"t1").start();
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
new Thread(() ->{
boolean b = atomicInteger.compareAndSet(100, 102);
System.out.println(Thread.currentThread().getName()+"\t"+"修改成功后与否:"+b+"\t"+atomicInteger.get());
},"t2").start();
}
}
输出结果表明,线程t2成功地将变量值从100修改为102,但实际上变量值已经经历了从100到101再到100的变化。这种情况下,CAS操作无法检测到中间的变化,从而导致安全性问题。
解决办法
为了解决ABA问题,可以采用版本号机制。具体来说,每次对变量进行修改时,都会更新一个版本号。CAS操作在比较变量值的同时,也会比较版本号,只有当值和版本号都匹配时,才会执行更新操作。
升级加固-版本号机制
下面通过代码示例说明如何使用版本号机制解决ABA问题:
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicStampedReference;
public class ABADemo {
// 演示ABA
static AtomicInteger atomicInteger = new AtomicInteger(100);
static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100,1);
public static void main(String[] args) {
new Thread(()->{
int stamp = atomicStampedReference.getStamp();
System.out.println("当前版本号:"+stamp);
atomicStampedReference.compareAndSet(100,101,stamp,stamp+1);
int stamp2 = atomicStampedReference.getStamp();
System.out.println("当前版本号:"+stamp2);
atomicStampedReference.compareAndSet(101,100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
int stamp3 = atomicStampedReference.getStamp();
System.out.println("当前版本号:"+stamp3);
},"t2").start();
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
通过引入版本号机制,每次对变量进行修改时都会更新版本号,从而避免了ABA问题。
高级示例
下面是一个更复杂的示例,展示了如何在多线程环境下使用版本号机制避免ABA问题:
import java.util.concurrent.atomic.AtomicStampedReference;
public class AvoidABADemo {
public static void main(String[] args) {
AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);
new Thread(() -> {
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "\t首次版本号:" + stamp); // 1
// 暂停一会儿线程
try {
Thread.sleep(1000);
} 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());
}, "t3").start();
new Thread(() -> {
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "\t 首次版本号:" + stamp); // 1
// 暂停一会儿线程,获得初始值100和初始版本号1,故意暂停3秒钟让t3线程完成一次ABA操作产生问题
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean result = atomicStampedReference.compareAndSet(100, 2019, stamp, stamp + 1);
System.out.println(Thread.currentThread().getName() + "\t" + result + "\t" + atomicStampedReference.getReference());
}, "t4").start();
}
}
在这个示例中,线程t3先将变量值从100修改为101,再修改回100,同时更新版本号。线程t4在等待3秒钟后尝试将变量值从100修改为2019,但由于版本号不匹配,修改操作失败,从而避免了ABA问题。
热门推荐
活动平板运动试验--筛查冠心病的好帮手
阿基米德与浮力定律:一个流传千古的科学发现故事
打造卓越游戏体验:游戏主机硬件配置指南及性能优选建议
外国人申请中国驾照的流程与要求
尿酸高是怎么回事就要如何解决
平陆运河:700 亿投资背后的价值与广西发展新篇
神经网络中的损失函数(Loss Function)
企业创新六大支柱:从文化到效益的全方位指南
海归回国求职,留学生如何界定应届生身份?
白内障手术有后遗症吗?术后注意事项全解析
欧美妆容立体修容教程
「动脉硬化斑块」竟可完全消退!JACC颠覆性大发现:这5件事必须做好
揭秘电影票房统计:以《哪吒2》为例分析导演收入来源
误工费没有工资证明应该如何赔偿
职场指南丨公司停工停产,我该怎么办?
智慧文旅管理系统:如何同时满足景区、游客和管理部门三方需求
正确识别心源性猝死!记住这些急救措施,关键时候能救命!
个人独资企业转让价格查询指南
普通高中的学业考试剖析与准备
契税、增值税双降,500万房产立省至少10万元
《赎罪》:一部以战争、爱情和救赎为主题的历史剧情片
四川广安机场项目获批,成渝地区双城经济圈迎来新航空枢纽
严查保险“黑中介”,香港保监局提示“转介绍”经纪模式不合法
云盘中的视频如何分享
为什么“日有所思,夜有所梦”?来看“梦境”的科普吧~
做好科学教育加法,科素课堂在行动
抖音短视频拍摄:选择合适画质与创意提升观众吸引力
CBA动态:郭艾伦伤情成焦点,赵睿展现队长风范,布莱克尼回归引关注
什么是卡片编程?一种创新的编程教育方法
如何合法查询惠州房价?查询过程中有哪些注意事项?