JavaEE之CAS

打印 上一主题 下一主题

主题 858|帖子 858|积分 2578

上文我们熟悉了许很多多的锁,此篇我们的CAS就是从上文的锁计谋开展的新概念,我们来一探毕竟吧
  1. 什么是CAS?

CAS: 全称Compare and swap,字⾯意思:“比力并交换”,⼀个CAS涉及到以下操作:
我们假设内存中的原数据V,旧的预期值A,必要修改的新值B。

  • 比力A与V是否相等。(比力)
  • 如果比力相等,将B写入V。(交换)
  • 返回操作是否成功。
CAS伪代码
下面写的代码不是原子的,真实的CAS是⼀个原子的硬件指令完成的.这个伪代码只是辅助明白CAS的工作流程.
  1. while{
  2. boolean CAS(address, expectValue, swapValue) {
  3. if (&address == expectedValue) {
  4. &address = swapValue;
  5. return true;
  6. }
  7. return false;
  8. }
  9. }
复制代码
说明:

  • address: 表现要修改的内存所在
  • expectValue: 预期值
  • swapValue:要设置的新值
执行流程:

  • 用一个预期值去和内存中的值做比力
  • 如果预期值与内存中的值相等,就用新的值更新内存中的值
  • 如果预期值与内存中的值不相等,就用进入下一次比力
我们前面学习过的线程安全题目是由于CPU的随机调度导致的,我们的处理方法是给有关修改共享变量的代码块加了synchronize关键字,保证了原子性,解决了线程安全题目,
那么题目来了,
为什么我们看到的CAS伪代码并没有保证原子性,那么他是如何保证原子性的呢??
我们从一段例子来讲解:




总结:

  • CAS对应的是硬件指令cmpxchg从CPU做了原子性支持,与LOCK 、UNLOCK 指令同地位
  • CAS操作每次读取(LOAD)数据是从主内存中读取,并没有对应的工作内存
  • 自旋锁是通过while把CAS进行包裹,让CAS没有成功的时候不停的执行,直到成功执行为止
  • CAS是一个真正的指令,JVM调用了本地方法之后,对应的CAS指令会在CPU上执行
    执行中LOAD数据会访问主内存
2. CAS实现自旋锁

基于CAS实现更灵活的锁,获取到更多的控制权.
自旋锁伪代码:
  1. public class SpinLock {
  2. private Thread owner = null;
  3. public void lock(){
  4. // 通过CAS 看当前锁是否被某个线程持有.  
  5. // 如果这个锁已经被别的线程持有, 那么就⾃旋等待.  
  6. // 如果这个锁没有被别的线程持有, 那么就把owner 设为当前尝试加锁的线程.  
  7. }
  8. }
  9. while(!CAS(this.owner, null, Thread.currentThread())){
  10. }
  11. public void unlock (){
  12. this.owner = null;
  13. }
复制代码
3. JDK中基于CAS实现的原子类

说明:

  • JDK中提供了CAS实现的原子类,我们举此中一种AtomicInteger类来说明

  • 我们调用此中的方法getAndIncrement()
  1. //自增 ++i
  2. atomicInteger.getAndIncrement();
复制代码

  • 我们发现getAndIncrement()方法本质是getAndIncrement()
  1. public final int getAndIncrement() {
  2.         return U.getAndAddInt(this, VALUE, 1);
  3. }
复制代码

  • 此中调用了Unsafe这个类的方法,是JDK内部的工具类,不发起外部程序员直接调用
  1. private static final Unsafe U = Unsafe.getUnsafe();
  2. //获取内存中字段在对象中的偏移地址
  3.     private static final long VALUE
  4.         = U.objectFieldOffset(AtomicInteger.class, "value");
复制代码


  1. //1. o表示原子类对象
  2. //2. offset表示内存中字段在对象中的偏移地址
  3. //3. delta表示传入的值
  4. public final int getAndAddInt(Object o, long offset, int delta) {
  5.         int v;
  6.         do {
  7.         //v是CAS操作之前获取的预期值
  8.             v = getIntVolatile(o, offset);
  9.         } while (!weakCompareAndSetInt(o, offset, v, v + delta));
  10.         //while不停的自旋
  11.         return v;
  12.     }
复制代码


  1.    public final boolean weakCompareAndSetInt(Object o, long offset,//内存地址
  2.                                               int expected,//预期值
  3.                                               int x) {//要设置的值
  4.         return compareAndSetInt(o, offset, expected, x);//最后调用compareAndSetInt()方法
  5.     }
复制代码


  1. //native表示本地方法,即对应了一条CAS指令cmpxchg
  2. public final native boolean compareAndSetInt(Object o, long offset,
  3.                                                  int expected,
  4.                                                  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,也可以使用时间戳
  1. System.out.println(UUID.randomUUID());
复制代码
天生随机数:


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

王國慶

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表