当前位置:网站首页>CAS机制
CAS机制
2022-07-07 08:23:00 【HGW689】
1、什么是CAS?
CAS的全称是 Compare And Swap(比较再交换,确切一点称之为:比较并且相同再做交换)
是现代CPU广泛支持的一种对内存中的共享数据进行操作的一种特殊指令。CAS的作用是:CAS可以将比较和交换转换为原子操作,这个原子操作直接由处理器CPU保证。
(可以看做是一个轻量级的synchronized,它能保证变量修改的原子操作)
CAS指令需要有三个操作数,分别是:
- 内存位置(在Java中可以简单地理解为变量的内存地址,用V表示)
- 旧的预取值(用A表示)
- 准备设置的新值(用B表示)
CAS指令执行时,当且仅当 V 符合 A 时,处理器才会用 B 更新 V 的值,否则它就不执行更新 或 重来(当他重来重试的这种行为称为——自旋)。但是不管是否更新了 V 的值,都会返回 V 的旧值。该过程是一个原子操作,执行期间不会被其他线程中断。
它是一种CPU并发原语,原语属于操作系统用语范畴,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题。
说了这么多原理,撸一下Demo吧~通过实现类AtomicInteger来演示一下CAS:
public class CASDemo {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(6);
System.out.println(atomicInteger.compareAndSet(6, 2022) + "\t" + atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(6, 2022) + "\t" + atomicInteger.get());
}
}
第一次C的值等于A,故将C改为B。第二次C得值不等于A,故不修改。
2、CAS实现原子操作的3大问题?
ABA问题、循环时间长消耗资源大、只能保证一个 共享变量的原子操作。
循环时间长消耗资源大
比如说源码 getAndAddInt方法执行时,有个 do while。如果CAS失败,会一直进行尝试。如果CAS长时间一直不成功,可能会给CPU带来很大的开销。
ABA 问题
如果一个变量 V 初次读取的时候是 A 值,并且在准备赋值的时候检查到它仍然为 A 值,那就能说明它的值没有被其他线程改变过了吗?
这是不可能的,因为 如果在这段期间它的值曾经被改成 B,后来又被改回为 A,那CAS操作就会误人误它从来没有被改变过。这个漏洞称为 CAS 操作的 “ABA问题”。接下来个Demo~
public class ABADemo {
static AtomicInteger atomicInteger = new AtomicInteger(100);
public static void main(String[] args) {
new Thread(()->{
atomicInteger.compareAndSet(100,200);
// 暂停10毫秒
try {
TimeUnit.MILLISECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
atomicInteger.compareAndSet(200,100);
}
},"t1").start();
new Thread(()->{
// 暂停200毫秒
try {
TimeUnit.MILLISECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(atomicInteger.compareAndSet(100,500) + "\t" + atomicInteger.get());
}
},"tw").start();
}
}
代码中 t1线程将值修改为200,但又在t2线程读取之前修改为100。但t1线程不知道呀,就会误人误它从来没有被改变过。
那么如何解决ABA问题呢?可以使用java.util.concurrent.atomic.AtomicStampedReference<V>
类来解决
public class ABADemo {
static AtomicInteger atomicInteger = new AtomicInteger(100);
static AtomicStampedReference<Integer> stampedReference = new AtomicStampedReference<>(100,1);
public static void main(String[] args) {
new Thread(()->{
int stamp = stampedReference.getStamp();
System.out.println(Thread.currentThread().getName()+"\t"+"首次版本号:"+stamp);
// 暂停500毫秒,保证后面的t2线程初始化拿到的版本号和t1一样
try {
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
stampedReference.compareAndSet(100, 200, stampedReference.getStamp(), stampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName()+"\t"+"2次版本号:"+stampedReference.getStamp());
stampedReference.compareAndSet(200,100,stampedReference.getStamp(), stampedReference.getStamp()+1);
},"t1").start();
new Thread(()->{
int stamp = stampedReference.getStamp();
System.out.println(Thread.currentThread().getName()+"\t"+"首次版本号:"+stamp);
// 暂停1000毫秒,保证t2线程初始化拿到的版本号和t1一样
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean b = stampedReference.compareAndSet(100, 500, stamp, stampedReference.getStamp() + 1);
System.out.println(b + "\t" + stampedReference.getReference() + "\t" + stampedReference.getStamp());
},"t2").start();
}
}
3、Unsafe类
从 JDK5 之后,Java类库中才开始使用CAS操作,该操作由 sun.misc.Unsafe 类里面的 compareAndSwapXXX()
方法包底层实现即为CPU指令cmpxchg。
执行 cmpxchg 指令的时候,会判断当前系统是否为多核系统,如果是就给总线加锁,只有一个线程会对总线加锁成功,加锁成功会执行CAS操作,也就是说CAS的原子性实际上是CPU实现独占的。
Unsafe类详解
1、Unsafe
是CAS的核心类,由于Java方法无法直接访问底层系统,需要通过本地(native)方法来访问,Unsafe相当于一个后门,基于该类可以直接操作特定内存的数据。Unsafe类存在于sun.misc包中,其内部方法操作可以像C的指针一样直接操作内存,因为Java中CAS操作的执行依赖于Unsafe类的方法。
注意:Unsafe类中的所有方法都是native修饰的,也就是说Unsafe类中的方法都是直接调用操作系统底层资源执行相应任务。
2、变量valueOffset,表示该变量值在内存中的偏移地址,因为Unsafe就是根据内存偏移地址获取数据的。
3、变量value用volatile修饰,保证了多线程之间的内存可见性。
源码解读
接下来我们来分析一下源代码:
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
通过点进compareAndSet() 方法的源代码,发现其是调用了unsafe.compareAndSwapInt()方法,在unsafe类中,主要有以下三个方法:
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
上面三个方法都是类似的,主要对4个参数做一下说明:
- var1:表示要操作的对象
- var2:表示要操作对象中属性地址的偏移量
- var4:表示需要修改数据的期望的值
- var5/var6:表示需要修改为的新值
这里对偏移量做一下讲解,大学汇编里有说过~this 相当于当前对象的首地址,需要找到对应的value在内存中的存放位置,此时就需要一个偏移量,即:收地址+偏移量=值在内存中的位置
我们已知i++在多线程情况下是不安全的,那 atomicInteger.getAndIncrement() 方法呢?
假设线程A和现场B两个线程同时执行 getAndIncrement()方法(分别跑在不同CPU上):
- 假设主内存中 value原始值为3,根据JMM模型,线程A 和 线程B各自持有一份值为3的value的副本分别到各自的工作内存。
- 线程A通过getIntVolatile(var1, var2)拿到value值3,假设这时线程A被挂起。
- 线程B也通过getIntVolatile(var1, var2)拿到value值3,此时线程B没有被挂起并执行 compareAndSwapInt 方法,比较内存值也为3,则成功修改内存值为4,线程B执行完毕。
- 此时线程A被唤醒,执行compareAndSwapInt 方法比较,发现主内存中的值和旧的预期值不一致,说明该值已经被其他线程更新了,则线程A本次修改失败,自旋重来一次。
- 线程A重新获取value值,因为变量value被volatile修饰,所以其他线程对它的修改,线程A是可见的,线程A继续执行 compareAndSwapInt 进行比较替换,直到成功为止。
Unsafe类中的compareAndSwapInt,对应着本地方法,该方法的实现位于unsafe.cpp,让我们一探究竟~
4、AtomicReference<V>
AtomicReference和AtomicInteger非常类似,不同之处就在于AtomicInteger是对整数的封装,而AtomicReference则对应普通的对象引用。也就是它可以保证你在修改对象引用时的线程安全性。
AtomicReference是作用是对”对象”进行原子操作。 提供了一种读和写都是原子性的对象引用变量。原子意味着多个线程试图改变同一个AtomicReference(例如比较和交换操作)将不会使得AtomicReference处于不一致的状态。
即 可以原子更新的对象引用。
首先编写一个User类:
class User{
String username;
int age;
// 省略全参构造方法、setter、getter、toString
}
通过AtomicReference类实现对”User对象”进行原子操作。
public class AtomicReferenceDemo {
public static void main(String[] args) {
AtomicReference<User> userAtomicReference = new AtomicReference<>();
User hgw = new User("hgw", 22);
User hly = new User("hly", 22);
userAtomicReference.set(hly);
System.out.println(userAtomicReference.compareAndSet(hly, hgw) +"\t,"+ userAtomicReference.get());
System.out.println(userAtomicReference.compareAndSet(hly, hgw) +"\t,"+ userAtomicReference.get());
}
}
5、CAS——自旋锁
谈CAS的话当然得谈谈自旋锁啦,这边手写个自旋锁给大家演示一下吧~
public class SpinLockDemo {
AtomicReference<Thread> atomicReference = new AtomicReference<>();
public void lock() {
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"\t"+"----come in");
while (!atomicReference.compareAndSet(null, thread)) {
}
System.out.println(Thread.currentThread().getName()+"\t"+"lock");
}
public void unLock() {
Thread thread = Thread.currentThread();
atomicReference.compareAndSet(thread,null);
System.out.println(Thread.currentThread().getName()+"\t"+"----task over,unLock...");
}
public static void main(String[] args) {
SpinLockDemo spinLockDemo = new SpinLockDemo();
new Thread(()->{
spinLockDemo.lock();
// 暂停几秒钟线程
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
spinLockDemo.unLock();
}
},"A").start();
new Thread(()->{
spinLockDemo.lock();
spinLockDemo.unLock();
},"B").start();
}
}
边栏推荐
- Use the fetch statement to obtain the repetition of the last row of cursor data
- Programming features of ISP, IAP, ICP, JTAG and SWD
- Kotlin实现微信界面切换(Fragment练习)
- Weekly recommended short videos: what are the functions of L2 that we often use in daily life?
- Hdu-2196 tree DP learning notes
- IDA中常见快捷键
- 学习记录——高精度加法和乘法
- 555电路详解
- Slurm资源管理与作业调度系统安装配置
- Study summary of postgraduate entrance examination in September
猜你喜欢
成为优秀的TS体操高手 之 TS 类型体操前置知识储备
Pdf document signature Guide
STM32 ADC和DMA
Several schemes of building hardware communication technology of Internet of things
0x0fa23729 (vcruntime140d.dll) (in classes and objects - encapsulation.Exe) exception thrown (resolved)
01 use function to approximate cosine function (15 points)
Experience sharing of software designers preparing for exams
1324:【例6.6】整数区间
Prototype and prototype chain
MySQL insert data create trigger fill UUID field value
随机推荐
mysql插入数据创建触发器填充uuid字段值
When there are pointer variable members in the custom type, the return value and parameters of the assignment operator overload must be reference types
fiddler-AutoResponder
EasyExcel读取写入简单使用
Appx code signing Guide
STM32 ADC和DMA
Serial communication relay Modbus communication host computer debugging software tool project development case
宁愿把简单的问题说一百遍,也不把复杂的问题做一遍
IIC基本知识
Postman interface test VI
Pdf document signature Guide
leetcode-304:二维区域和检索 - 矩阵不可变
STM32 product introduction
Jump to the mobile terminal page or PC terminal page according to the device information
大整数类实现阶乘
字符串格式化
HDU-2196 树形DP学习笔记
移动端通过设置rem使页面内容及字体大小自动调整
Factorial implementation of large integer classes
【acwing】789. 数的范围(二分基础)