CAS底层原理与ABA问题
CAS定义
CAS(Compare And Swap)是一种无锁算法。CAS算法是乐观锁的一种实现。CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当预期值A和内存值V相同时,将内存值V修改为B并返回true,否则返回false。
CAS与synchronized
(1)synchronized加锁,同一时间段只允许一个线程访问,能够保证一致性但是并发性下降。
(2)CAS是一个自旋锁算法,使用do-while不断判断(没有加锁),保证一致性和并发性,但是比较消耗CPU资源。使用CAS就可以不用加锁来实现线程安全。
- 原子性保证:CAS算法依赖于rt.jar包下的sun.misc.Unsafe类,该类中的所有方法都是native修饰的,直接调用操作系统底层资源执行相应的任务。
- 内存可见性和禁止指令重排序的保证:AtomicXxx类中的成员变量value是由volatile修饰的:private volatile int value;
CAS算法的缺点
CAS虽然很高效的解决了原子操作问题,但是CAS仍然存在三大问题。
- 循环时间长、开销很大。
当某一方法比如:getAndAddInt执行时,如果CAS失败,会一直进行尝试。如果CAS长时间尝试但是一直不成功,可能会给CPU带来很大的开销。
- 只能保证一个共享变量的原子操作。
当操作1个共享变量时,我们可以使用循环CAS的方式来保证原子操作,但是操作多个共享变量时,循环CAS就无法保证操作的原子性,这个时候就需要用锁来保证原子性。
- 存在ABA问题
如果一个线程在初次读取时的值为A,并且在赋值的时候检查该值仍然是A,但是可能在这两次操作,之间有另外一个线程现将变量的值改成了B,然后又将该值改回为A,那么CAS会误认为该变量没有变化过。
CAS底层原理
sum.misc.Unsafe类中有多个方法被native关键字标记,这说明该方法是原生态的方法,它是一个调用非java语言的接口,也就是说这个接口的实现是其他语言实现的。CAS并发原语就是体现在java的sum.misc.Unsafe类中的各个方法,调用这个类中的CAS方法JVM就会通过其他语言生成若干条系统指令,完整这些指令的过程中,是不允许被中断的,所以CAS是一条CUP的原子指令,所以它不会造成数据不一致问题。
多线程情况下,number变量每次++都会出现线程安全问题,AtomicInteger则不会,因为它保证了原子性。
我们进去看,getAndIncrement调用的就是Unsafe类中的getAndAddInt方法,this表示当前对象,valueOffset表示变量值在内存中的偏移量(也就是内存地址)
我们再进入Unsafe类看看var1就是getAndIncrement方法传过来的对象,var2是系统偏移量,这里是使用了do-while循环,一开始循环就通过var1对象和var2偏移量获取期望值var5,进入循环,compareAndSwapInt方法被native关键字标记的,所以他是原子性的 ,var2的值与var的值相等时,则使用新的值var5+var4,返回true,循环条件取反则结束循环,否则如果var2与var5不相等就继续循环,直到条件不满足再跳出循环
// unsafe.class
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
// 获取对象var1,偏移量为var2地址上的值,并赋值给var5
var5 = this.getIntVolatile(var1, var2);
/**
* 再次获取对象var1,偏移量var2地址上的值,并和var5进行比较:
* - 如果不相等,返回false,继续执行do-while循环
* - 如果相等,将返回的var5数值和var4相加并返回
*/
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
// 最终总是返回对象var1,偏移量为var2地址上的值,即上述所说的V。
return var5;
}
ABA问题解决方案
使用AtomicStampedReference或者AtomicMarkableReference来解决CAS的ABA问题,思路类似于SVN版本号,SpringBoot热部署中trigger.txt
AtomicStampedReference解决方案:每次修改都会让stamp值加1,类似于版本控制号
package com.raicho.mianshi.mycas; import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference; /**
* @author: Raicho
* @Description:
* @program: mianshi
* @create: 2020-07-17 10:19
**/
public class AtomicStampedReferenceABA {
private static AtomicReference<Integer> ar = new AtomicReference<>(0);
private static AtomicStampedReference<Integer> asr =
new AtomicStampedReference<>(0, 1); public static void main(String[] args) {
System.out.println("=============演示ABA问题(AtomicReference)===========");
new Thread(() -> {
ar.compareAndSet(0, 1);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
ar.compareAndSet(1, 0);
System.out.println(Thread.currentThread().getName() + "进行了一次ABA操作");
}, "子线程").start(); try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
} boolean res = ar.compareAndSet(0, 100);
if (res) {
System.out.println("main成功修改, 未察觉到子线程进行了ABA操作");
} System.out.println("=============解决ABA问题(AtomicStampReference)===========");
new Thread(() -> {
int curStamp = asr.getStamp();
System.out.println("t1获取当前stamp: " + curStamp);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
asr.compareAndSet(0, 1, curStamp, curStamp + 1);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
asr.compareAndSet(1, 0, asr.getStamp(), asr.getStamp() + 1);
}, "t1").start(); new Thread(() -> {
int curStamp = asr.getStamp();
System.out.println("t2获取当前stamp: " + curStamp);
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean result = asr.compareAndSet(0, 100, curStamp, curStamp + 1);
if (!result) {
System.out.println("修改失败! 预期stamp: " + curStamp + ", 实际stamp: " + asr.getStamp());
}
}, "t2").start();
}
}
运行结果:
AtomicMarkableReference:如果不关心引用变量中途被修改了多少次,而只关心是否被修改过,可以使用AtomicMarkableReference:
package com.raicho.mianshi.mycas; import java.util.concurrent.atomic.AtomicMarkableReference; /**
* @author: Raicho
* @Description:
* @program: mianshi
* @create: 2020-07-17 10:46
**/
public class AtomicMarkableReferenceABA {
private static AtomicMarkableReference<Integer> amr = new AtomicMarkableReference<>(0, false); public static void main(String[] args) {
new Thread(() -> {
amr.compareAndSet(0, 1, false, true);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
amr.compareAndSet(1, 0, true, true);
System.out.println("子线程进行了ABA修改!");
}, "子线程").start(); try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
} boolean res = amr.compareAndSet(0, 100, false, true);
if (!res) {
System.out.println("修改失败! 当前isMarked: " + amr.isMarked());
}
}
}
运行结果:
参考
知乎:https://zhuanlan.zhihu.com/p/93418208
csdn:https://blog.csdn.net/justry_deng/article/details/83449038
CAS底层原理与ABA问题的更多相关文章
- java面试-CAS底层原理
一.CAS是什么? 比较并交换,它是一条CPU并发原语. CAS是一种无锁算法,CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B.当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什 ...
- java - CAS及CAS底层原理
CAS是什么? CAS的全称为Compare-And-Swap它是一条CPU并发原语,也就是在CPU硬件层面上来说比较并且判断是否设置新值这段操作是原子性的,不会被其他线程所打断.在JAVA并发包ja ...
- 并发编程之 CAS 的原理
前言 在并发编程中,锁是消耗性能的操作,同一时间只能有一个线程进入同步块修改变量的值,比如下面的代码 synchronized void function(int b){ a = a + b: } 如 ...
- CAS(乐观锁)与ABA问题
cas是什么 CAS 全称 compare and swap 或者compare and exchange 比较并且交换.用于在没有锁的情况下,多个线程对同一个值的更新. cas原理 例如,我们对一 ...
- 并发之volatile底层原理
15.深入分析Volatile的实现原理 14.java多线程编程底层原理剖析以及volatile原理 13.Java中Volatile底层原理与应用 12.Java多线程-java.util.con ...
- 一文彻底搞懂CAS实现原理 & 深入到CPU指令
本文导读: 前言 如何保障线程安全 CAS原理剖析 CPU如何保证原子操作 解密CAS底层指令 小结 朋友,文章优先发布公众号,如果你愿意,可否扫文末二维码关注下? 前言 日常编码过程中,基本不会直接 ...
- 最简单的HashMap底层原理介绍
HashMap 底层原理 1.HashMap底层概述 2.JDK1.7实现方式 3.JDK1.8实现方式 4.关键名词 5.相关问题 1.HashMap底层概述 在JDK1.7中HashMap采用的 ...
- Java面试底层原理
面试发现经常有些重复的面试问题,自己也应该学会记录下来,最好自己能做成笔记,在下一次面的时候说得有条不紊,深入具体,面试官想必也很开心.以下是我个人总结,请参考: HashSet底层原理:(问了大几率 ...
- Java8线程池ThreadPoolExecutor底层原理及其源码解析
小侃一下 日常开发中, 或许不会直接new线程或线程池, 但这些线程相关的基础或思想是非常重要的, 参考林迪效应; 就算没有直接用到, 可能间接也用到了类似的思想或原理, 例如tomcat, jett ...
随机推荐
- Jmeter系列(21)- 详解 HTTP Request
如果你想从头学习Jmeter,可以看看这个系列的文章哦 https://www.cnblogs.com/poloyy/category/1746599.html HTTP Request 介绍 用来发 ...
- Spring事务深入剖析--spring事务失效的原因
之前我们讲的分布式事务的调用都是在一个service中的事务方法,去调用另外一个service中的业务方法, 如果在一个sevice中存在两个分布式事务方法,在一个seivice中两个事务方法相互嵌套 ...
- 使用 Masstransit中的 Request/Response 与 Courier 功能实现最终一致性
简介 目前的.net 生态中,最终一致性组件的选择一直是一个问题.本地事务表(cap)需要在每个服务的数据库中插入消息表,而且做不了此类事务 比如:创建订单需要 余额满足+库存满足,库存和余额处于两个 ...
- Code Walkthroughs DataStream API
上级:https://www.cnblogs.com/hackerxiaoyon/p/12747387.html DataStream API DataStreamApi 提供了健壮,有状态的流应用, ...
- Mariadb之显式使用表锁和行级锁
首先我们来看看mariadb的锁定概念,所谓锁就是当一个进程或事务在操作某一资源时,为了防止其他用户或者进程或事务对其进行资源操作,导致资源抢占而发生冲突,通常在A进程操作该资源时,会对该资源进行加锁 ...
- js语法基础入门(4)
4.运算符 4.1.什么是运算符? 运算符就是用来表示具体运算规则的符号,例如数学计算中的加减乘除就是具体的运算规则,我们分别用"+ - * /"等符号来表示 4.2.运算符的分类 ...
- 不同编程语言实现HelloWorld程序
目录 C C# C++ HTML Java Python C #include <stdio.h> int main() { printf("Hello World!" ...
- 深入探究ASP.NET Core异常处理中间件
前言 全局异常处理是我们编程过程中不可或缺的重要环节.有了全局异常处理机制给我们带来了很多便捷,首先我们不用满屏幕处理程序可能出现的异常,其次我们可以对异常进行统一的处理,比如收集异常信息或者 ...
- JavaScript基础对象创建模式之沙盘模式(026)
沙盘模式可以弥补命名空间模式中的两项不足之处: 使用唯一全局对象作为程序的全局变量入口,使得无法在同一程序中使用两个不同版本的API,因此它们使用的是同一个唯一的全局对象名,如MYAPP: 较长的嵌套 ...
- Spring 获取单例流程(二)
读完这篇文章你将会收获到 Spring 中 prototype 类型的 bean 如何做循环依赖检测 Spring 中 singleton 类型的 bean 如何做循环依赖检测 前言 继上一篇文章 S ...