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 ...
随机推荐
- 福州大学软工1816 K 班助教总结
春节时期总有各种诱惑因素(例如路人超能第二季),拖到现在才发布十分抱歉orz. 一.感谢 首先对柯老师和软工助教指导团队这一学期以来的支持和指导表示感谢.第一次做助教,有时候会提出一些不大成熟的想法, ...
- 打开ubuntu终端的两个方法【最快速】
两种快捷方法: 1. ctrl+alt+T. 2. 桌面右击,再点击终端.
- css border 制作三角形
border 边框 上三角 是只有上面的border 有颜色,其余的边框都是tranparents,下三角只有下面的border 有颜色,其余的边框都是tranparents,左三角只有左面的bord ...
- soap 简单的例子
首先确保你的soap模块开启 客户端代码 <?php try { $client = new SoapClient(null, array('location' =>"http: ...
- [转帖]overlay文件系统解析
overlay文件系统解析 来源:http://dockone.io/article/1511 原作者: 陈爱珍 布道师@七牛云 一个 overlay 文件系统包含两个文件系统,一个 upper 文件 ...
- mappers标签引入映射器的四种方式
第一种方式:mapper标签,通过resource属性引入classpath路径的相对资源 <!-- Using classpath relative resources --> < ...
- 【jQuery插件】pagepiling滚屏插件使用
pagePiling.js 这款 jQuery 插件可以帮助前端开发人员轻松实现全屏滚动效果. 支持所有的主流浏览器,包括IE8+,支持移动设备.下面详细讲解下pagePiling.js的使用步骤. ...
- 调用webservice超时问题的解决[转]
1.web.config配置,<system.web></system.web>里面增加:<httpRuntime maxRequestLength="1024 ...
- BZOJ3439 Kpm的MC密码(可持久化trie)
将串反过来就变成查询前缀了.考虑建一棵可持久化trie,查询时二分答案,均摊一下复杂度即为O(mlogn). #include<iostream> #include<cstdio&g ...
- BZOJ 1283: 序列
1283: 序列 Time Limit: 10 Sec Memory Limit: 162 MBSubmit: 272 Solved: 151[Submit][Status][Discuss] D ...