CAS你知道吗?底层如何实现?ABA问题又是什么?关于这些你知道答案吗
CAS你知道吗?如何实现?
1. compareAndSet
在volatile
当中我们提到,volatile
不能保证原子语义,所以当用到变量自增时,如果用到synchronized
会太”重“了,在多线程环境下我们一般用原子类如AtomicInteger
,其底层是CAS,volatile见此篇
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
上述代码表示:
- 如果线程的期望值和物理内存的真实值一样,那么就修改为更新值
- 如果不一样,本次修改失败,就需要重新获取主物理内存的值
简单的代码例子:
package com.yuxue.juc.CASTest;
import java.util.concurrent.atomic.AtomicInteger;
/**
* CAS:比较并交换
*/
public class CASDemo {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger();
//compareAndSet返回的是boolean类型,修改成功返回true,失败返回false
System.out.println(atomicInteger.compareAndSet(0, 666) + "\t current data is " + atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(1, 777) + "\t current data is " + atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(666, 888) + "\t current data is " + atomicInteger.get());
}
}
输出为:
//第一次期望值是0,原值默认0,所以CAS成功修改为666
true current data is 666
//第二次期望值是1,原值第一步修改为666,所以CAS不成功修改
false current data is 666
//第三次期望值是666,原值第一步修改为666,所以CAS成功修改为888
true current data is 888
2. CAS底层原理?对Unsafe的理解
我们都知道,atomicInteger.getAndIncrement()
方法能够在多线程环境下保证变量的安全同时让其自增,但是源码当中也没有synchronized,那么如何保证底层安全?如果保证多线程环境下的变量安全?我们打开其源码:
2.1 compareAndSet
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
里面有用到unsafe
对象的compareAndSwapInt
方法,再找unsafe源码
其底层用到Unsafe类来保证线程安全!
2.2 Unsafe
是CAS核心类,由于Java方法无法直接访问地层系统,需要通过本地(native)方法来访问,Unsafe相当 于一个后门,基于该类可以直接操作特定内存数据。Unsafe类存在于
sun.misc
包中,其内部方法操作可 以像C的指针一样直接操作内存,因为Java中CAS操作的执行依赖于Unsafe类的方法。Unsafe类中的所有方法都是native修饰的,也就是说Unsafe类中的方法都直接调用操作系统底层资源执行相应任务
变量valueOffset,表示该变量值在内存中的偏移地址,因为Unsafe就是根据内存偏移地址获取数据的
变量value用
volatile
修饰,保证多线程之间的可见性
2.3 CAS是什么
CAS全称呼Compare-And-Swap
,它是一条CPU并发原语
- 他的功能是判断内存某个位置的值是否为预期值,如果是则更改为新的值,这个过程是原子的
- CAS并发原语体现在JAVA语言中就是
sun.misc.Unsafe
类中各个方法。调用Unsafe类中的CAS方法,JVM会帮我们实现CAS汇编指令。这是一种完全依赖于硬件的功能,通过他实现了原子操作。 - 由于CAS是一种系统原语,原语属于操作系统用语范畴,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成数据不一致问题
所以!CAS是通过Unsafe类的CPU指令源语来保证数据的原子性!
CAS源码:
//unsafe.getAndAddInt
//var1对应
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = getIntVolatile(o, offset);
} while (!compareAndSwapInt(o, offset, v, v + delta));
return v;
}
o this即AtomicInteger
对象本身
offset 该对象的引用地址(偏移地址)
delta 需要增加的变量
v通过AtomicInteger
对象本身的offset偏移地址找出的主内存中真实的值,用该对象前的值与v比较; 如果相同,更新v+delta并且返回true, 如果不同,继续去之然后再比较,直到更新完成
2.4 总结
CAS:比较当前工作内存中的值和主物理内存中的值,如果相同则执行规定操作,否则的话继续比较直到主内存和工作内存中的值一值!(不清楚工作内存以及主内存的请移步查看volatile中的JMM模型
3. CAS缺点
3.1 循环时间长,开销大
例如getAndAddInt
方法执行,有个do...while
循环,如果CAS失败,一直会进行尝试,如果CAS长时间不成功, 可能会给CPU带来很大的开销(自旋!)
3.2 只能保证一个共享变量的原子操作
对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁来保证原子性
3.3 ABA问题
CAS算法实现一个重要前提需要去除内存中某个时刻的数据并在当下时刻比较并替换,那么在这个时间差类会导致数据的变化
例:比如线程1从内存位置V取出A,线程2同时也从内存取出A,并且线程2进行一些操作将值改为B,然后线程2又将V位置数据改成A,这时候线程1进行CAS操作发现内存中的值依然时A,然后线程1操作成功
尽管线程1的CAS操作成功,但是不代表这个过程没有问题
4. 原子类AtomicInteger的ABA问题?原子更新引用?
首先我们已经阐述了ABA的概念以及问题,首先我们要知道原子引用类的概念
4.1 原子引用
AtomicReference<V>
这里的V只要是其他的类均可使用AtomicReference
作为其包装类
示例代码:
package com.yuxue.juc.CASTest;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.concurrent.atomic.AtomicReference;
@Data
@NoArgsConstructor
@AllArgsConstructor
class User{
private String name;
private int age;
}
/**
* 测试原子引用类
* */
public class AtomicReferenceTest {
public static void main(String[] args) {
User u1 = new User("张三",18);
User u2 = new User("李四",23);
AtomicReference<User> atomicReference = new AtomicReference<>();
atomicReference.set(u1);
System.out.println(atomicReference.compareAndSet(u1, u2) + "\t" + atomicReference.get().toString());
System.out.println(atomicReference.compareAndSet(u1, u2) + "\t" + atomicReference.get().toString());
}
}
输出结果为:
true User(name=李四, age=23)
false User(name=李四, age=23)
4.2 ABA问题代码实现
解决方案:带时间戳的原子引用
首先ABA问题代码展示:
package com.yuxue.juc.CASTest;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;
public class ABASolution {
static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);
public static void main(String[] args) {
new Thread(() -> {
//ABA问题
System.out.println(atomicReference.compareAndSet(100, 101) + "\t" + Thread.currentThread().getName() + "value is:" + atomicReference.get());
System.out.println(atomicReference.compareAndSet(101, 100) + "\t" + Thread.currentThread().getName() + "value is:" + atomicReference.get());
}, "t1").start();
new Thread(() -> {
//先休眠,让t1线程完成ABA操作
try {
Thread.sleep(1000);
System.out.println(atomicReference.compareAndSet(100, 2019) + "\t" + Thread.currentThread().getName() + "value is:" + atomicReference.get());
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t2").start();
}
}
结果为:
true t1 value is:101
true t1 value is:100
true t2 value is:2019
可以看到t1首先修改值为101,之后又修改回来100,但是线程t2的工作内存中还是100,之后与主内存相比,发现主内存值也是100,之后放心修改值为2019,此时就会出现ABA问题
4.3 所以?怎么解决ABA问题?
采用内置的类AtomicStampedReference<V>
其为携带时间戳的类,我们可以每次更改值时对时间戳进行操作,这样就可以保证不会出现ABA问题
package com.yuxue.juc.CASTest;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;
public class ABASolution {
//创建变量,第一个是initialRef为初始值,第二个是initialStamp为初始化时间戳
static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);
public static void main(String[] args) {
//atmoinReferenceMethod();
new Thread(() -> {
//获得时间戳,此时为1
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "\t第1次版本号" + stamp);
try {
//此处休眠的目的是为了让t2获得初始版本号
Thread.sleep(1000);
//第一次修改,值改为101,版本号加1
atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
System.out.println(Thread.currentThread().getName() + "\t第2次版本号" + atomicStampedReference.getStamp());
//第二次修改,值改为100,版本号加1
atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
System.out.println(Thread.currentThread().getName() + "\t第3次版本号" + atomicStampedReference.getStamp());
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t1").start();
new Thread(() -> {
//获得时间戳,此时为1
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "\t第1次版本号" + stamp);
try {
//休眠2秒,此时线程t1已经将值改变但是又变回来,为ABA问题
Thread.sleep(2000);
//首先尝试是否可以根据值和时间戳进行更改
boolean result = atomicStampedReference.compareAndSet(100, 2019, stamp, stamp + 1);
System.out.println(Thread.currentThread().getName() + "\t修改是否成功" + result + "\t当前最新实际版本号" + atomicStampedReference.getStamp());
System.out.println(Thread.currentThread().getName() + "\t当前最新实际值" + atomicStampedReference.getReference());
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t2").start();
}
}
输出结果:
t1 第1次版本号1
t2 第1次版本号1
//t1修改两次,版本号加了2
t1 第2次版本号2
t1 第3次版本号3
//t2判断版本号,之后再决定能不能改
t2 修改是否成功false 当前最新实际版本号3
//实际并没有进行更改
t2 当前最新实际值100
这样,我们用JUC内置atomic下的AtomicStampedReference
类来解决了ABA问题
CAS你知道吗?底层如何实现?ABA问题又是什么?关于这些你知道答案吗的更多相关文章
- CAS底层原理与ABA问题
CAS定义 CAS(Compare And Swap)是一种无锁算法.CAS算法是乐观锁的一种实现.CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B.当预期值A和内存值V相同时,将内存值V修 ...
- java高并发核心要点|系列3|锁的底层实现原理|ABA问题
继续讲CAS算法,上篇文章我们知道,CAS算法底层实现,是通过CPU的原子指令来实现. 那么这里又有一个情景: 话说,有一个线程one从内存位置V中取出A,这时候另一个线程two也从内存中取出A,并且 ...
- 基础篇:详解锁原理,volatile+cas、synchronized的底层实现
目录 1 锁的分类 2 synchronized底层原理 3 Object的wait和notify方法原理 4 jvm对synchronized的优化 5 CAS的底层原理 6 CAS同步操作的问题 ...
- CAS核心思想、底层实现
★ 1.CAS 是什么 CAS 是比较并交换,是实现并发算法时常用到的一种技术.当内存的值和期望的值相等时,进行更新,否则 什么都不做 或 重来 . CAS 的底层实现:是靠硬件实现的,靠硬件的原子性 ...
- 沉淀再出发:java中的CAS和ABA问题整理
沉淀再出发:java中的CAS和ABA问题整理 一.前言 在多并发程序设计之中,我们不得不面对并发.互斥.竞争.死锁.资源抢占等等问题,归根到底就是读写的问题,有了读写才有了增删改查,才有了所有的一切 ...
- 非阻塞式的原子性操作-CAS应用及原理
一:问题抛出 假设在出现高并发的情况下对一个整数变量做依次递增操作,下面这两段代码是否会出现问题? 1. public class IntegerTest { private static Integ ...
- CAS 无锁式同步机制
计算机系统中,CPU 和内存之间是通过总线进行通信的,当某个线程占有 CPU 执行指令的时候,会尽可能的将一些需要从内存中访问的变量缓存在自己的高速缓存区中,而修改也不会立即映射到内存. 而此时,其他 ...
- 详解java中CAS机制所导致的问题以及解决——内存顺序冲突
[CAS机制] 指的是CompareAndSwap或CompareAndSet,是一个原子操作,实现此机制的原子类记录着当前值的在内存中存储的偏移地址,将内存中的真实值V与旧的预期值A做比较,如果不一 ...
- 无锁的同步策略——CAS操作详解
目录 1. 从乐观锁和悲观锁谈起 2. CAS详解 2.1 CAS指令 2.3 Java中的CAS指令 2.4 CAS结合失败重试机制进行并发控制 3. CAS操作的优势和劣势 3.1 CAS相比独占 ...
随机推荐
- calico官网网络拓扑实现:基于eNSP与VMVare
Calico官网提供了两种网络设计模式: AS per rack: 每个rack(机架)组成一个AS,每个rack的TOR交换机与核心交换机组成一个AS AS per server: 每个node做为 ...
- [物联网] 电气 & 工控
原理 一次回路和二次回路 一次回路:强电部分(380伏---22万伏),连接发电机.电动机.变压器.电网线路.电网开关.电网避雷器等等 二次回路:弱电部分,指的是控制线路.保护线路.测量线路.计量线路 ...
- hive beeline详解
Hive客户端工具后续将使用Beeline 替代HiveCLI ,并且后续版本也会废弃掉HiveCLI 客户端工具,Beeline是 Hive 0.11版本引入的新命令行客户端工具,它是基于SQLLi ...
- VMware虚拟机性能优化
一.ESX及vCenter服务器的优化 检查ESX物理服务器是否在兼容列表中,特别是BIOS的版本是否符合ESX版本的要求 开启ESX物理服务器硬件虚拟化技术VT-X,AMD-V 关闭BIOS中的英特 ...
- kubernetes架构及deployment应用(4)
Kubernetes Cluster 由 Master 和 Node 组成,节点上运行着若干 Kubernetes 服务. 一.master节点 Master 是 Kubernetes Cluster ...
- git cherry-pick(不同分支的提交合并)
git cherry-pick可以选择某一个分支中的一个或几个commit(s)来进行操作.例如,假设我们有个稳定版本的分支,叫v2.0,另外还有个开发版本的分支v3.0,我们不能直接把两个分支合并, ...
- IDEA Git 项目实战场景
实战场景一:上班啦,从远程仓库克隆项目到本地仓库(Clone) 打开 IDEA,在 Check out from Version Control 下拉菜单选择 Git,如下: 在弹出窗口的 URL 地 ...
- 2.2 CPU 上下文切换是什么意思?(下)
怎么查看系统的上下文切换情况 过多的上下文切换,会把 CPU 时间消耗在寄存器.内核栈以及虚拟内存等数据的保存和恢复上,缩短进程真正运行的时间,成了系统性能大幅下降的一个 元凶. 使用 vmstat ...
- 10.4 route:显示或管理路由表
route命令 可以显示或管理Linux系统的路由表,route命令设置的路由主要是静态路由. 路由的概念 计算机与计算机之间的数据传输必须得经由网络,而网络可以通过直接连接两台计算机的方式或 ...
- VS Code 安装后的一些配置项
说明: 个人一直使用Notepad++作为日常文本编辑器,由于之前出现的某个原因,故决定改用VS Code. •设置中文字体 • 输入快捷键 Ctrl+Shift+P • 输入 Configure D ...