Java并发编程原理与实战四十三:CAS ---- ABA问题
CAS(Compare And Swap)导致的ABA问题
问题描述
多线程情况下,每个线程使用CAS操作欲将数据A修改成B,当然我们只希望只有一个线程能够正确的修改数据,并且只修改一次。当并发的时候,其中一个线程已经将A成功的改成了B,但是在线程并发调度过程中尚未被调度,在这个期间,另外一个线程(不在并发中的请求线程)将B又修改成了A,那么原来并发中的线程又可以通过CAS操作将A改成B
测试用例:
public class AbaPro {
private static final Random RANDOM = new Random();
private static final String B = "B";
private static final String A = "A";
public static final AtomicReference<String> ATOMIC_REFERENCE = new AtomicReference<>(A);
public static void main(String[] args) throws InterruptedException {
final CountDownLatch startLatch = new CountDownLatch(1);
Thread[] threads = new Thread[20];
for (int i=0; i < 20; i++){
threads[i] = new Thread(){
@Override
public void run() {
String oldValue = ATOMIC_REFERENCE.get();
try {
startLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
Thread.sleep(RANDOM.nextInt()&500);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (ATOMIC_REFERENCE.compareAndSet(oldValue, B )){
System.out.println(Thread.currentThread().getName()+ " 已经对原始值进行了修改,此时值为: "+ ATOMIC_REFERENCE.get());
}
}
};
threads[i].start();
}
startLatch.countDown();
Thread.sleep(200);
new Thread(){
@Override
public void run() {
try {
Thread.sleep(RANDOM.nextInt() & 200);
} catch (InterruptedException e) {
e.printStackTrace();
}
String oldVal = ATOMIC_REFERENCE.get();
while (!ATOMIC_REFERENCE.compareAndSet(ATOMIC_REFERENCE.get(), A));
System.out.println(Thread.currentThread().getName() +" 已经将值 "+oldVal+" 修改成原始值: A");
}
}.start();
}
}
结果:
Thread-12 已经对原始值进行了修改,此时值为: B
Thread-20 已经将值 B 修改成原始值: A
Thread-14 已经对原始值进行了修改,此时值为: B
可以看到并发中的线程Thread-12已经成功的将A修改成B,其他线程Thread-20在某一时刻将B修改成A,而并发中的线程Thread-14又能再次成功的将A修改成B,虽然最终结果是B,但是中途经历了一次被修改的过程,在某些情况下是致使的
解决方案
java中提供了AtomicStampedReference来解决这个问题,它是基于版本或者是一种状态,在修改的过程中不仅对比值,也同时会对比版本号
public class AabProResolve {
private static final Random RANDOM = new Random();
private static final String B = "B";
private static final String A = "A";
private static final AtomicStampedReference<String> ATOMIC_STAMPED_REFERENCE = new AtomicStampedReference<>(A,0);
public static void main(String[] args) throws InterruptedException {
final CountDownLatch startLatch = new CountDownLatch(1);
Thread[] threads = new Thread[20];
for (int i=0; i < 20; i++){
threads[i] = new Thread(){
@Override
public void run() {
String oldValue = ATOMIC_STAMPED_REFERENCE.getReference();
int stamp = ATOMIC_STAMPED_REFERENCE.getStamp();
try {
startLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
Thread.sleep(RANDOM.nextInt() & 500);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (ATOMIC_STAMPED_REFERENCE.compareAndSet(oldValue, B, stamp, stamp+1)){
System.out.println(Thread.currentThread().getName()+ " 已经对原始值: "+oldValue+" 进行了修改,此时值为: "+ ATOMIC_STAMPED_REFERENCE.getReference());
}
}
};
threads[i].start();
}
Thread.sleep(200);
startLatch.countDown();
new Thread(){
@Override
public void run() {
try {
Thread.sleep(RANDOM.nextInt() & 200);
} catch (InterruptedException e) {
e.printStackTrace();
}
int stamp = ATOMIC_STAMPED_REFERENCE.getStamp();
String oldVal = ATOMIC_STAMPED_REFERENCE.getReference();
while (!ATOMIC_STAMPED_REFERENCE.compareAndSet(
B,
A,stamp, stamp+1)){
stamp = ATOMIC_STAMPED_REFERENCE.getStamp();
}
System.out.println(Thread.currentThread().getName() +" 已经将值 "+oldVal+" 修改成原始值: A");
}
}.start();
}
}
结果:
Thread-1 已经对原始值: A 进行了修改,此时值为: B
Thread-20 已经将值 B 修改成原始值: A
可以看到并发期间的线程只有Thread-1对A进行了修改,保证了只有一个线程对数据的修改,短暂的并发时间之后的其他线程Thread-20对其修改自然也就没有影响、
优化方案,可以参考:
https://blog.csdn.net/wufaliang003/article/details/78797203
Java并发编程原理与实战四十三:CAS ---- ABA问题的更多相关文章
- Java并发编程原理与实战四十二:锁与volatile的内存语义
锁与volatile的内存语义 1.锁的内存语义 2.volatile内存语义 3.synchronized内存语义 4.Lock与synchronized的区别 5.ReentrantLock源码实 ...
- Java并发编程原理与实战三十三:同步容器与并发容器
1.什么叫容器? ----->数组,对象,集合等等都是容器. 2.什么叫同步容器? ----->Vector,ArrayList,HashMap等等. 3.在多线程环境下,为什么不 ...
- Java并发编程原理与实战四十:JDK8新增LongAdder详解
传统的原子锁AtomicLong/AtomicInt虽然也可以处理大量并发情况下的计数器,但是由于使用了自旋等待,当存在大量竞争时,会存在大量自旋等待,而导致CPU浪费,而有效计算很少,降低了计算效率 ...
- Java并发编程原理与实战四十五:问题定位总结
背景 “线下没问题的”. “代码不可能有问题 是系统原因”.“能在线上远程debug么” 线上问题不同于开发期间的bug,与运行时环境.压力.并发情况.具体的业务相关.对于线上的问题利用线上 ...
- Java并发编程原理与实战四十一:重排序 和 happens-before
一.概念理解 首先我们先来了解一下什么是重排序:重排序是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段. 从Java源代码到最终实际执行的指令序列,会分别经历下面3种重排序,如下图 ...
- Java并发编程原理与实战四十四:final域的内存语义
一.final域的重排序规则 对于final域,编译器和处理器要遵循两个重拍序规则: 1.在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序 ...
- Java并发编程原理与实战二十三:Condition原理分析
先来回顾一下java中的等待/通知机制 我们有时会遇到这样的场景:线程A执行到某个点的时候,因为某个条件condition不满足,需要线程A暂停:等到线程B修改了条件condition,使condit ...
- Java并发编程原理与实战四:线程如何中断
如果你使用过杀毒软件,可能会发现全盘杀毒太耗时间了,这时你如果点击取消杀毒按钮,那么此时你正在中断一个运行的线程. java为我们提供了一种调用interrupt()方法来请求终止线程的方法,下面我们 ...
- Java并发编程原理与实战五:创建线程的多种方式
一.继承Thread类 public class Demo1 extends Thread { public Demo1(String name) { super(name); } @Override ...
随机推荐
- 简单Window下 Android Studio的安装
(1)首先安装JDK 下载JDK 本人觉得官方网站下JDK比较慢,可以直接百度JDK,(如果是64位 百度搜索记得+64位)
- 蜗牛慢慢爬 LeetCode 7. Reverse Integer [Difficulty: Easy]
题目 Reverse digits of an integer. Example1: x = 123, return 321 Example2: x = -123, return -321 Have ...
- Selenium_RC环境配置
上一篇里,对Selenium IDE自动录制的脚本进行了一下分析,按照前两篇的操作,很明显,新建的 那个Test.py文件是不能运行的.关键问题在于1.没有装Python:2.没有装Selenium_ ...
- java 常见的异常大集合
算术异常类:ArithmeticExecption 空指针异常类:NullPointerException 类型强制转换异常:ClassCastException 数组负下标异常:NegativeAr ...
- Python基础【3】:Python中的深浅拷贝解析
深浅拷贝 在研究Python的深浅拷贝区别前需要先弄清楚以下的一些基础概念: 变量--引用--对象(可变对象,不可变对象) 切片(序列化对象)--拷贝(深拷贝,浅拷贝) 我是铺垫~ 一.[变量--引用 ...
- Java的Bean
Bean的定义 遵循Sun的Java Bean规范编写的特殊类 Java Bean的规范 类的访问控制权限是public 类提供有一个无参的构造函数 类的属性的访问控制权限是private,通过set ...
- 【版本管理】git本地操作
1.初始化一个Git仓库,使用git init命令. 2.添加文件到Git仓库,分两步: • 第一步,使用命令git add 文件名,注意,可反复多次使用,添加多个文件: • 第二步,使用命令git ...
- 调用webservice超时问题的解决[转]
1.web.config配置,<system.web></system.web>里面增加:<httpRuntime maxRequestLength="1024 ...
- SPOJ NETADMIN_Smart Network Administrator
给一个图,某些点需要单独以某一种颜色的线连接到1点,问如何安排能够使得整个图颜色最多的一条路颜色最少. 显然,二分枚举然后加以颜色其实就是流量了,相当于对每条边限定一个当前二分的流量值,判断能否满流即 ...
- java 表单验证
java 表单验证 1.思路:通过表单选择器,表单属性过滤器提取每个表单提交的值,进行验证 2.实现:javascript通过 onSubmit()事件,判断,返回值false不提交,返回true提交 ...