上文我们熟悉了许很多多的锁,此篇我们的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伪代码并没有保证原子性,那么他是如何保证原子性的呢??
我们从一段例子来讲解:
总结:
- 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类来说明
- 我们调用此中的方法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);
复制代码 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());
复制代码 天生随机数:
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |