cas解析

2023-05-16

JAVA CAS原理、unsafe、AQS

concurrent包的实现

由于java的CAS同时具有 volatile 读和volatile写的内存语义,因此Java线程之间的通信现在有了下面四种方式:

  1. A线程写volatile变量,随后B线程读这个volatile变量。
  2. A线程写volatile变量,随后B线程用CAS更新这个volatile变量。
  3. A线程用CAS更新一个volatile变量,随后B线程用CAS更新这个volatile变量。
  4. A线程用CAS更新一个volatile变量,随后B线程读这个volatile变量。

Java的CAS会使用现代处理器上提供的高效机器级别原子指令,这些原子指令以原子方式对内存执行读-改-写操作,这是在多处理器中实现同步的关键(从 本质上来说,能够支持原子性读-改-写指令的计算机器,是顺序计算图灵机的异步等价机器,因此任何现代的多处理器都会去支持某种能对内存执行原子性读-改 -写操作的原子指令)。同时,volatile变量的读/写和CAS可以实现线程之间的通信。把这些特性整合在一起,就形成了整个concurrent包 得以实现的基石。如果我们仔细分析concurrent包的源代码实现,会发现一个通用化的实现模式:

  1. 首先,声明共享变量为volatile;
  2. 然后,使用CAS的原子条件更新来实现线程之间的同步;
  3. 同时,配合以volatile的读/写和CAS所具有的volatile读和写的内存语义来实现线程之间的通信。

AQS,非阻塞数据结构和原子变量类(java.util.concurrent.atomic包中的类),这些concurrent包中的基础类都是使 用这种模式来实现的,而concurrent包中的高层类又是依赖于这些基础类来实现的。从整体来看,concurrent包的实现示意图如下:

CAS

CAS:Compare and Swap, 翻译成比较并交换。 

java.util.concurrent包中借助CAS实现了区别于synchronouse同步锁的一种乐观锁。

本文先从CAS的应用说起,再深入原理解析。

CAS应用

CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。

非阻塞算法 (nonblocking algorithms)

一个线程的失败或者挂起不应该影响其他线程的失败或挂起的算法。

现代的CPU提供了特殊的指令,可以自动更新共享数据,而且能够检测到其他线程的干扰,而 compareAndSet() 就用这些代替了锁定。

拿出AtomicInteger来研究在没有锁的情况下是如何做到数据正确性的。


private volatile int value;  

首先毫无以为,在没有锁的机制下可能需要借助volatile原语,保证线程间的数据是可见的(共享的)。

这样才获取变量的值的时候才能直接读取。


    public final int get() {
        return value;
    }  

然后来看看++i是怎么做到的。

复制代码


public final int incrementAndGet() {
    for (;;) {
        int current = get();
        int next = current + 1;
        if (compareAndSet(current, next))
            return next;
    }
}  

复制代码

在这里采用了CAS操作,每次从内存中读取数据然后将此数据和+1后的结果进行CAS操作,如果成功就返回结果,否则重试直到成功为止。

而compareAndSet利用JNI来完成CPU指令的操作:


public final boolean compareAndSet(int expect, int update) {   
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}  

整体的过程就是这样子的,利用CPU的CAS指令,同时借助JNI来完成Java的非阻塞算法。其它原子操作都是利用类似的特性完成的。

其中

unsafe.compareAndSwapInt(this, valueOffset, expect, update);

类似:


if (this == expect) {
  this = update
 return true;
} else {
return false;
}  

那么问题就来了,成功过程中需要2个步骤:比较this == expect,替换this = update,compareAndSwapInt如何这两个步骤的原子性呢? 参考CAS的原理。

CAS原理

 CAS通过调用JNI的代码实现的。JNI:Java Native Interface为JAVA本地调用,允许java调用其他语言。

compareAndSwapInt就是借助C来调用CPU底层指令实现的

下面从分析比较常用的CPU(intel x86)来解释CAS的实现原理。

 下面是sun.misc.Unsafe类的compareAndSwapInt()方法的源代码:


public final native boolean compareAndSwapInt(Object o, long offset,
                                              int expected,
                                              int x);  

可以看到这是个本地方法调用。这个本地方法在openjdk中依次调用的c++代码为:unsafe.cpp,atomic.cpp和atomicwindowsx86.inline.hpp。这个本地方法的最终实现在openjdk的如下位置:openjdk-7-fcs-src-b147-27jun2011\openjdk\hotspot\src\oscpu\windowsx86\vm\ atomicwindowsx86.inline.hpp(对应于windows操作系统,X86处理器)。下面是对应于intel x86处理器的源代码的片段:

复制代码


// Adding a lock prefix to an instruction on MP machine
// VC++ doesn't like the lock prefix to be on a single line
// so we can't insert a label after the lock prefix.
// By emitting a lock prefix, we can define a label after it.
#define LOCK_IF_MP(mp) __asm cmp mp, 0  \
                       __asm je L0      \
                       __asm _emit 0xF0 \
                       __asm L0:

inline jint     Atomic::cmpxchg    (jint     exchange_value, volatile jint*     dest, jint     compare_value) {
  // alternative for InterlockedCompareExchange
  int mp = os::is_MP();
  __asm {
    mov edx, dest
    mov ecx, exchange_value
    mov eax, compare_value
    LOCK_IF_MP(mp)
    cmpxchg dword ptr [edx], ecx
  }
}  

复制代码

如上面源代码所示,程序会根据当前处理器的类型来决定是否为cmpxchg指令添加lock前缀。如果程序是在多处理器上运行,就为cmpxchg指令加 上lock前缀(lock cmpxchg)。反之,如果程序是在单处理器上运行,就省略lock前缀(单处理器自身会维护单处理器内的顺序一致性,不需要lock前缀提供的内存屏 障效果)。

 intel的手册对lock前缀的说明如下:

  1. 确保对内存的读-改-写操作原子执行。在Pentium及Pentium之前的处理器中,带有lock前缀的指令在执行期间会锁住总线,使得其他 处理器暂时无法通过总线访问内存。很显然,这会带来昂贵的开销。从Pentium 4,Intel Xeon及P6处理器开始,intel在原有总线锁的基础上做了一个很有意义的优化:如果要访问的内存区域(area of memory)在lock前缀指令执行期间已经在处理器内部的缓存中被锁定(即包含该内存区域的缓存行当前处于独占或以修改状态),并且该内存区域被完全 包含在单个缓存行(cache line)中,那么处理器将直接执行该指令。由于在指令执行期间该缓存行会一直被锁定,其它处理器无法读/写该指令要访问的内存区域,因此能保证指令执行 的原子性。这个操作过程叫做缓存锁定(cache locking),缓存锁定将大大降低lock前缀指令的执行开销,但是当多处理器之间的竞争程度很高或者指令访问的内存地址未对齐时,仍然会锁住总线。
  2. 禁止该指令与之前和之后的读和写指令重排序。
  3. 把写缓冲区中的所有数据刷新到内存中。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

cas解析 的相关文章

随机推荐

  • 谈谈你对依赖注入(DI)和控制反转(IoC)的理解

    学习过Spring框架的人一定都会听过Spring的IoC 控制反转 DI 依赖注入 这两个概念 xff0c 对于初学Spring的人来说 xff0c 总觉得IoC DI这两个概念是模糊不清的 xff0c 是很难理解的 xff0c 今天和大
  • Guava Cache应用以及源码解析

    Guava Cache的学习 https github com google guava 一 Guava的适用性 二 两种缓存的加载 1 第一种CacheLoader方式 xff0c 返回的是LoadingCache对象 xff0c 这个对
  • java邮箱激活

    一 分析 xff1a 1 xff1a 先从前端接收到一个邮箱帐号 xff0c 由于鲁棒性的要求 xff0c 在发送邮箱之前邮箱格式必须正确 xff01 xff01 不然的话第一发送不了 xff0c 第二会发生错误 xff0c 出现异常 所以
  • 解决Count and Say

    一 题目 xff1a The count and say sequence is the sequence of integers with the first five terms as following 1 1 2 11 3 21 4
  • 大数据概述

    一 大数据定义 量大 复杂 二 四个特征以及应用场景 三 大数据技术 1 展现与交互 报表 图形 可视化工具 增强现实 2 数据计算 查询 统计 分析 预测 挖掘 图谱 BI 3 数据存储 分布式文件系统 xff0c 分布式数据库 4 数据
  • hdfs的理解以及shell命令

    一 hdfs的实现思想 xff1a 1 hdfs是利用分布式集群来存储文件的 xff0c 为客户端提供一个便捷的访问方式 xff0c 就是一个虚拟的目录结构 2 文件存储的时候是被分割成若干的block块的 3 文件的bloc块存放在若干台
  • js中的prop()和attr()方法

    以下两种方法等价 xff1a span class token operator lt span input id span class token operator 61 span span class token string 34 t
  • iOS富文本(NSAttributedString)---尽力弄全了

    把简书文章搬过来 最近浮躁 xff0c 毛线都没写 xff0c 不断有人关注点赞我 xff0c 必须总结点干货了 项目上要加载html格式的文本 xff0c 学习一下富文本相关内容 1 加载HTML标签文本 因为解析的数据里面有html标签
  • $ is not defined之SpringMVC中关于jsp中的ajax连接不到controller的问题

    刚刚写完jsp中的ajax xff0c 发现Controller路径名称以及取得的参数取得都正确 xff0c 文本域中的触发函数也正确 xff0c 可就是触发onblur方法的时候 xff0c 发现Controller并没有反应 经过调试后
  • codem2018年初赛A轮

    第六题 小美创建了一套算法 xff0c 第一行输入两个整数 xff0c a和b xff0c 第二行输入一个字符串c 假如a b的小数部分中包含第三个输入的数c xff0c 则输出c在小数部分出现的位置k xff0c 如果不包含 xff0c
  • 接口和抽象类的区别

    在interface里面的变量都是public static final 的 所以你可以这样写 xff1a public static final int i 61 10 或则 int i 61 10 xff1b xff08 可以省略掉一部
  • 动态代理

    动态代理是在不改变原来方法的代码的前提下 xff0c 用来增强原来方法的功能的 在程序的角度上来说 xff0c 就是说让别人来帮助自己完成更加强的功能 xff0c 别人就是动态代理对象 java中的动态代理由两个核心的组件来完成 xff0c
  • 数据库的读写分离和负载均衡

    mysql的数据库读写分离是为了要解决如何在复制集群的不同角色上 xff0c 去执行不同的sql语句 读尽量分布到从服务器上 xff0c 写只能在主服务器上 读的负载均衡则是解决如何在相同的从服务器上分担相同的sql语句的问题 读的负载均衡
  • NIO,BIO,AIO的区别和联系

    一个IO操作其实分成了两个步骤 xff1a 发起IO请求和实际的IO操作 IO操作可以分为3类 xff1a 同步阻塞 xff08 即早期的IO操作 xff09 同步非阻塞 xff08 NIO xff09 异步 xff08 AIO xff09
  • springmvc中文件的上传和下载

    步骤 xff1a 1 在前端的form表单中申明enctype 61 34 multipart form data 34 2 在前端的文件区域设置yourfile lt input type 61 34 file 34 name 61 34
  • git的使用

    管理员从master分支创建develop分支用于开发 git checkout b develop RD从远程仓库pull最新的develop分支 xff0c 并拉个feature分支用于需求开发 git pull origin deve
  • JAVA内存模型剖析

    java内存模型剖析 xff1a A xff1a 内存模型是什么鬼 xff1a 一 cpu和缓存一致性 计算机中cpu要和数据打交道 xff0c 而数据往往是放到主存中去的 xff0c 所以就可以理解成cpu和主存打交道 随着cpu的不断优
  • mybatis学习

    mybatis学习 xff1a 一 jdbc存在的问题 xff1a 1 频繁创建链接 xff0c 造成系统资源的浪费 2 sql语句死板 xff0c 造成sql语句硬编码的问题 3 代码太繁琐 xff0c 维护难 二 mybatis学习改进
  • mysql中Access denied(using password:NO)问题解决

    我使用mysql启动命令启动了mysql后发现了下面错误 这个错误是因为前期我没有对数据库设置密码 xff0c 这里进入mysql报了错误 经过网上查找 xff0c 下面的步骤成功解决了我的问题 在安装mysql的文件夹下找到my int这
  • cas解析

    JAVA CAS原理 unsafe AQS concurrent包的实现 由于java的CAS同时具有 volatile 读和volatile写的内存语义 xff0c 因此Java线程之间的通信现在有了下面四种方式 xff1a A线程写vo