并发编程中的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问题。
热门推荐
山姆会员商店纵容代购、普通会员买不到限购商品?媒体调查
人民日报推荐:30本提升你视野、眼界和格局的好书
单射,满射和双射区分
警惕AI幻觉,普及人工智能教育,关键在培养学生自主学习能力
中国赛季:为何让全球网球迷热烈关注?
工程造价毕业论文选题指南:四大方向详解
苏东坡:中国文人的“精神绿洲”
以食之契约——陇西腊肉技能的传承(探寻传统手工制作陇西腊肉的工艺与味道)
女性在澳洲的就业前景:哪些行业最受欢迎?
国产化、信创和自主可控:概念、联系与实践
能量低时,与人聊聊,你就有力量了
智算与电力的深度融合:算电协同的价值与应用
永磁同步电机无感FOC滑膜观测器(SMO)原理与仿真分析
央视评论公共场所无烟诉讼第一案:我们期待的共识不止是禁烟
假期准备去厦门旅游该怎么规划?厦门五天四夜深度游玩攻略,这篇更省!
中世纪欧洲科学的传播交流流入与发展
警惕会“跑”的肿瘤——淋巴瘤
智能饮水机系统方案设计
你和咖啡店,就差一台咖啡机,超全咖啡机选购攻略
为什么拔牙要上午拔
浙江萧山9条特色一日游攻略:从亚运场馆到千年古镇
广州地铁11号线通车:换乘之王能否避免三号线重蹈覆辙?
文旅部推荐的这些湖南美食,有你家乡的吗?
沙门氏菌检测的重要性与方法
关于竹子的古诗:历史中的青翠之韵
全方位指南:护照与港澳通行证办理流程及地点解析
中医基础理论之藏象学说:五脏的生克乘侮关系
中药饮料圈粉年轻人!“AI医生”为你把脉定制
发酵设备:发酵罐的一般工作原理及操作流程详解
舌尖上的陕西:十大美食