JavaEE之CAS
上文我们熟悉了许很多多的锁,此篇我们的CAS就是从上文的锁计谋开展的新概念,我们来一探毕竟吧1. 什么是CAS?
CAS: 全称Compare and swap,字⾯意思:“比力并交换”,⼀个CAS涉及到以下操作:
我们假设内存中的原数据V,旧的预期值A,必要修改的新值B。
[*]比力A与V是否相等。(比力)
[*]如果比力相等,将B写入V。(交换)
[*]返回操作是否成功。
CAS伪代码
下面写的代码不是原子的,真实的CAS是⼀个原子的硬件指令完成的.这个伪代码只是辅助明白CAS的工作流程.
while{
boolean CAS(address, expectValue, swapValue) {
if (&address == expectedValue) {
&address = swapValue;
return true;
}
return false;
}
}
说明:
[*]address: 表现要修改的内存所在
[*]expectValue: 预期值
[*]swapValue:要设置的新值
执行流程:
[*]用一个预期值去和内存中的值做比力
[*]如果预期值与内存中的值相等,就用新的值更新内存中的值
[*]如果预期值与内存中的值不相等,就用进入下一次比力
我们前面学习过的线程安全题目是由于CPU的随机调度导致的,我们的处理方法是给有关修改共享变量的代码块加了synchronize关键字,保证了原子性,解决了线程安全题目,
那么题目来了,
为什么我们看到的CAS伪代码并没有保证原子性,那么他是如何保证原子性的呢??
我们从一段例子来讲解:
https://i-blog.csdnimg.cn/direct/6965b35df0d94cfd89c81623f42db7af.png
https://i-blog.csdnimg.cn/direct/fcddb3acf39840e0b75a6729d41a53f0.png
https://i-blog.csdnimg.cn/direct/56efb425a0f0421db6af86afaafcb0ec.png
https://i-blog.csdnimg.cn/direct/412eb7581d914440b655d875d9eda4ea.png
总结:
[*]CAS对应的是硬件指令cmpxchg从CPU做了原子性支持,与LOCK 、UNLOCK 指令同地位
[*]CAS操作每次读取(LOAD)数据是从主内存中读取,并没有对应的工作内存
[*]自旋锁是通过while把CAS进行包裹,让CAS没有成功的时候不停的执行,直到成功执行为止
[*]CAS是一个真正的指令,JVM调用了本地方法之后,对应的CAS指令会在CPU上执行
执行中LOAD数据会访问主内存
2. CAS实现自旋锁
基于CAS实现更灵活的锁,获取到更多的控制权.
自旋锁伪代码:
public class SpinLock {
private Thread owner = null;
public void lock(){
// 通过CAS 看当前锁是否被某个线程持有.
// 如果这个锁已经被别的线程持有, 那么就⾃旋等待.
// 如果这个锁没有被别的线程持有, 那么就把owner 设为当前尝试加锁的线程.
}
}
while(!CAS(this.owner, null, Thread.currentThread())){
}
public void unlock (){
this.owner = null;
}
3. JDK中基于CAS实现的原子类
说明:
[*]JDK中提供了CAS实现的原子类,我们举此中一种AtomicInteger类来说明
https://i-blog.csdnimg.cn/direct/7a6d552f16354772a6d446ff321d179d.png
[*]我们调用此中的方法getAndIncrement()
//自增 ++i
atomicInteger.getAndIncrement();
[*]我们发现getAndIncrement()方法本质是getAndIncrement()
public final int getAndIncrement() {
return U.getAndAddInt(this, VALUE, 1);
}
[*]此中调用了Unsafe这个类的方法,是JDK内部的工具类,不发起外部程序员直接调用
private static final Unsafe U = Unsafe.getUnsafe();
//获取内存中字段在对象中的偏移地址
private static final long VALUE
= U.objectFieldOffset(AtomicInteger.class, "value");
[*]
//1. o表示原子类对象
//2. offset表示内存中字段在对象中的偏移地址
//3. delta表示传入的值
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
//v是CAS操作之前获取的预期值
v = getIntVolatile(o, offset);
} while (!weakCompareAndSetInt(o, offset, v, v + delta));
//while不停的自旋
return v;
}
[*]
public final boolean weakCompareAndSetInt(Object o, long offset,//内存地址
int expected,//预期值
int x) {//要设置的值
return compareAndSetInt(o, offset, expected, x);//最后调用compareAndSetInt()方法
}
[*]
//native表示本地方法,即对应了一条CAS指令cmpxchg
public final native boolean compareAndSetInt(Object o, long offset,
int expected,
int x);
[*]图解如下:
https://i-blog.csdnimg.cn/direct/437e05d1b9a340fcb8e9104f5f41af25.png
4. CAS的ABA题目
首先我们要知道什么是ABA题目???
ABA的题目:
假设存在两个线程t1和t2.有一个共享变量value,初始值为A.
接下来,线程t1想使用CAS把value值改成Z,那么就必要
先读取value的值,记录到oldValue变量中.
使用CAS判断当前value的值是否为A,如果为A,就修改成Z.
但是,在t1执行这两个操作之间,t2线程可能把value的值从A改成了B,又从B改成了A
举个例子: 今天是疯狂星期四,我和我的女友共用一个小荷包用饭,我和她说如果小荷包内里有100元 (A状态) 晚上6点就点KFC吃,我怕她忘记了,我就先自己点了,此时,小荷包余额为50元 (B状态) 然后我的朋友疯狂星期四V我50,小荷包的钱又变回了100 (A状态),她再点的时候,发现小荷包还是100元,她就再点了一份,就导致多点了一份KFC
那么我们该如何解决ABA题目呢??
在点KFC的例子当中,我们可以让他查察一下斲丧记录(版本号),此时的100元是否是开始的100元,
由此可得,我们可以可以给value数据加上一个版本号,当线程2要修改时,不仅要value要符合预期值,同时版本号也要符合
即:
CAS操作在读取旧值的同时,也要读取版本号.
真正修改的时候,
[*]如果当前版本号和读到的版本号雷同,则修改数据,并把版本号+1.
[*]如果当前版本号高于读到的版本号.就操作失败(认为数据已经被修改过了).
版本号可以用自增的数值,也可以用随机的UUID,也可以使用时间戳
System.out.println(UUID.randomUUID());
天生随机数:
https://i-blog.csdnimg.cn/direct/541630ca8f1341478854217654b6343d.png
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]