CAS比较并交换(Compare and Swap)

1 基本原理

有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
CAS是一种典型的乐观锁, 假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。

2 实现原子操作

CAS操作分为获取当前值、比较和设置至少两部分,不是原子操作如何保证原子性呢。

2.1 JNI

compareAndSwapInt是借助C来调用CPU底层指令实现的,程序会根据当前处理器的类型来决定是否为cmpxchg指令加上lock前缀,也就是在多处理器上运行是提供内存屏障效果

2.1.1 原子执行

确保对内存的读-改-写操作原子执行
锁住总线
在Pentium及Pentium之前的处理器中,带有lock前缀的指令在执行期间会锁住总线,使得其他处理器暂时无法通过总线访问内存
优化
如果要访问的内存区域已经在处理器内部的缓存中被锁定(E或M),并且该内存区域被完全包含在单个缓存行中,将直接执行该指令
锁定CacheLine
由于在指令执行期间该缓存行会一直被锁定,其它处理器无法读/写该指令要访问的内存区域,因此能保证指令执行的原子性

2.1.2 禁止重排序

禁止该指令与之前和之后的读和写指令重排序

2.1.3 刷新内存

把写缓冲区中的所有数据刷新到内存中

3 缺点

CAS虽然很高效的解决原子操作,但是CAS仍然存在三大问题

ABA问题

如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了

解决:使用版本号( AtomicStampedReference)

循环时间长开销大

自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销

解决:JVM能支持处理器提供的pause指令那么效率会有一定的提升,也可以破坏循环或者将粒度变小,将一个变量拆分为多个变量

一个共享变量

只能保证一个共享变量的原子操作

解决:这个时候就可以用锁,或者有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作( AtomicReference)

MESI缓存一致性协议

多核CPU的情况下有多个一级缓存,如何保证内部数据的一致,就引出了缓存一致协议

1 CPU为何要有高速缓存

摩尔定律导致内存和硬盘的发展速度远远不及CPU

2 局部性原理

在CPU访问存储设备时,无论是存取数据或指令,都趋向于聚集在一片连续的区域中

2.1 时间局部性

如果一个信息项正在被访问,那么在近期它很可能还会被再次访问,比如循环、递归、方法的反复调用等

2.2 空间局部性

如果一个存储器的位置被引用,那么将来它附近的位置也很可能被引用,比如顺序执行的代码、连续创建的两个对象、数组等

3 CPU计算流程

带有高速缓存的CPU执行计算的流程
加载到主内存:程序以及数据被加载到主内存
加载到高速缓存:指令和数据被加载到CPU的高速缓存
写到高速缓存:CPU执行指令,把结果写到高速缓存
写回主内存:高速缓存中的数据写回到主内存

4 多级缓存

由于CPU的运算速度超越了一级缓存的数据I/O能力,CPU厂商又引入了多级的缓存结构
一级缓存:也叫内部缓存,封闭在CPU芯片内部,用于暂时存放CPU运算时的部分指令和数据,存取速度与CPU主频一致。
二级缓存(多级类似):也叫外部缓存,位于CPU外部,是一级缓存的缓冲器,存储那些CPU处理时需要用到、一级缓存又无法存储的数据。不能存储原始指令

5 缓存状态

M 修改

该缓存行只被缓存在该CPU缓存中,并且和主存数据不一致,需要在未来的某个时间点(允许其他CPU读取主存相应内容之前)写回主存,变成E
监听主存读取:必须时刻监听所有试图读取该缓存行对应的主存地址的操作,如果监听到,则必须将该缓存写回主存

E 独享

该缓存行只被缓存在该CPU的缓存中,是未被修改过的,与主存的数据一致,当有其他CPU读取该内存时,变成S,当该CPU修改该缓存行时变成M
监听主存读取:监听内容同M,监听到后需要把缓存行设置为S
优化:如果一个CPU想修改一个处于S状态的缓存行,总线事务需要将所有该缓存行的copy变成I状态,而修改E状态的缓存不需要使用总线事务

S 共享

该缓存行可能(作废)被多个CPU进行缓存,并且与主存一致,当有一个CPU修改该缓存行时,其他变为I
监听无效和独享:必须时刻监听使该缓存行无效或者独享该缓存行的请求,设置为I
不精确:缓存作废时不会广播,同时缓存不会保存该缓存的copy数量,因此无法确定是否已独享

I 作废

缓存作废时不会广播,同时缓存不会保存该缓存的copy数量,因此无法确定是否已独享

6 四个操作

local read读本地缓存

本地失效解除:一个无效的缓存必须从主存中读取(变为E或S),其他的本身即满足

local write写本地缓存

本地M化:本地缓存变M,S时须将其他缓存中该缓存行置为I,该操作经常用广播的方式来完成
其它无效:可以随时将一个非M状态的缓存行作废,或者变成I状态,M状态的必须先被写回主存

remote read读其它缓存

本地M/E共享:M或E会变成S,其它不变,注意,M会先同步到主存

remote write写其它缓存

本地无效:其它的写会导致所有状态的本地无效

7 MESI状态迁移表

AMD的Opteron处理器使用从MESI中演化出的MOESI协议,O(Owned)是MESI中S和M的一个合体,表示本Cache line被修改,和内存中的数据不一致,不过其它的核可以有这份数据的拷贝,状态为S。

Intel的core i7处理器使用从MESI中演化出的MESIF协议,F(Forward)从Share中演化而来,一个Cache line如果是Forward状态,它可以把数据直接传给其它内核的Cache,而Share则不能。

7 存在问题

阻塞问题

缓存切换状态时CPU会等待所有缓存响应完成,可能出现各种各样的性能问题和稳定性问题,比如你需要修改本地缓存中的一条信息,那么你必须将I(无效)状态通知到其他拥有该缓存数据的CPU缓存中,并且等待确认。等待确认的过程会阻塞处理器,这会降低处理器的性能。因为这个等待远远比一个指令的执行时间长的多。

存储缓存(store bufferes)

处理器把它想要写入到主存的值写到缓存,然后继续去处理其他事情。当所有失效确认(Invalidate Acknowledge)都接收到时,数据才会最终被提交。

这么做有两个风险

第一、就是处理器会尝试从存储缓存(Store buffer)中读取值,但它还没有进行提交。这个的解决方案称为Store Forwarding,它使得加载的时候,如果存储缓存中存在,则进行返回。这会导致“伪重排序”的问题

第二、保存什么时候会完成,这个并没有任何保证。

失效队列

处理失效的缓存也不是简单的操作,它需要处理器去处理。并且存储缓存也不是无限大的,那么当存储缓存满的时候,处理器还是要等待失效响应的,解决这种问题的思路是让invalidate ack能更早得返回。
入列:收到失效消息时,放到失效队列中去。
回复失效响应:为了不让处理器久等失效响应,收到失效消息需要马上回复失效响应。
等待一同处理:为了不频繁阻塞处理器,不会马上读主存以及设置缓存为invalid,合适的时候再一块处理失效队列。

内存屏障

存储缓存和失效队列会导致缓存问题,即可见性和顺序性,处理器并不知道什么时候优化是允许的,而什么时候并不允许。干脆处理器将这个任务丢给了写代码的人。这就是内存屏障
写之前提交存储缓存:写屏障Store Memory Barrier是一条告诉处理器在执行这之后的指令之前,应用所有已经在存储缓存(store buffer)中的保存的指令。
读之前应用失效队列:读屏障Load Memory Barrier是一条告诉处理器在执行任何的加载前,先应用所有已经在失效队列中的失效操作的指令。

伪共享

在多线程情况下,如果需要修改“共享同一个缓存行的变量”,就会无意中影响彼此的性能,这就是伪共享
举例:在多线程情况下,x,y两个共享变量在同一个缓存行中,核a修改变量x,会导致核b中的y变量同时失效
补齐:我们只需要前后填几个无用的变量补上>=64-N字节, 让不同的Volatile对象处于不同的缓存行, 就可以避免伪共享了
注解:Java8中新增了一个注解:@sun.misc.Contended。加上这个注解的类或字段会自动补齐缓存行,此注解默认是无效的,需要在jvm启动时设置-XX:-RestrictContended才会生效

8 MESI缓存一致性协议失效的原因

失效后果

当MESI失效之后,那么系统会自动将启用总线加锁机制,那么执行效率则会大打折扣。

失效情况

1.数据长度:当缓存行存储的数据超过最小存储单元大小时(数据长度存储跨越多个缓存行的情况),就会导致MESI操作缓存行无效,导致MESI缓存一致性协议失效;
2.不支持:系统不支持缓存一致性协议。

CAS和MESI和VOALTILE

1 volatile如何保证可见性

volatile,是怎么可见性的问题(CPU缓存),那么他是怎么解决的?加入volatile关键字时,会多出一个lock前缀指令,lock前缀指令实际上相当于一个内存屏障,它有三个功能:

重排序

确保指令重排序时不会把其后面的指令重排到内存屏障之前的位置,也不会把前面的指令排到内存屏障后面

立即刷新

将当前处理器缓存行的数据立即写回系统内存(由volatile先行发生原则保证);

无效嗅探(缓存失效)

会写内存(M-E)触发MESI local write导致其它缓存行无效remote write(也是由volatile先行发生原则保证);

2 CAS保证原子性

2.1 lock cmpxchg

在x86架构上,CAS被翻译为”lock cmpxchg…“,当两个core同时执行对同一地址的CAS指令时其实是在试图修改每个core持有的Cache line

2.2 锁定缓存行

由于在指令执行期间该缓存行会一直被锁定,其它处理器无法读/写该指令要访问的内存区域,因此能保证指令执行的原子性

2.3 总线仲裁协议

对于我们的CAS操作来说, 其实锁并没有消失,只是转嫁到了ring bus的总线仲裁协议中,只不过这个锁获取失败后不会等待

竞争invalidate

如果要由 S转为E或者M,两个core都会向ring bus发出 invalidate这个操作,在ringbus上就会仲裁谁能赢得这个invalidate

失败读取新值

胜者完成操作, 失败者需要接受结果, invalidate自己对应的cacheline,再读取胜者修改后的值, 回到起点

3 MESI和volatile

有了MESI协议,为什么还需要volatile这个关键词来保证可见性(内存屏障)?或者是只有加了volatile的变量在才会触发多核缓存一致性协议?

弱一致性

多核情况下,所有的cpu操作都会涉及缓存一致性的校验,只不过该协议是弱一致性,不能保证一个线程修改变量后,其他线程立马可见
存储缓存等导致延迟:也就是说虽然其他CPU状态已经置为无效,但是当前CPU可能将数据修改之后又去做其他事情,没有来得及将修改后的变量刷新回主存
立即刷新:如果此时其他CPU需要使用该变量,则又会从主存中读取到旧的值。而volatile则可以保证可见性,即立即刷新回主存;

MESI需要触发

另一种解释,正常情况下,系统操作并不会进行缓存一致性的校验,只有变量被volatile修饰了,该变量所在的缓存行才被赋予缓存一致性的校验功能。

CAS和MESI和VOALTILE相关推荐

  1. 从底层吃透java内存模型(JMM)、volatile、CAS

    前言 随着计算机的飞速发展,cpu从单核到四核,八核.在2020年中国网民数预计将达到11亿人.这些数据都意味着,作为一名java程序员,必须要掌握多线程开发,谈及多线程,绕不开的是对JMM(Java ...

  2. cas无法使用_一文彻底搞懂CAS实现原理

    本文导读: 前言 如何保障线程安全 CAS原理剖析 CPU如何保证原子操作 解密CAS底层指令 小结 前言 日常编码过程中,基本不会直接用到 CAS 操作,都是通过一些JDK 封装好的并发工具类来使用 ...

  3. java cas volatile_每日一个知识点:Volatile 和 CAS 的弊端之总线风暴

    每日一个知识点系列的目的是针对某一个知识点进行概括性总结,可在一分钟内完成知识点的阅读理解,此处不涉及详细的原理性解读. 一.什么是总线风暴 总线风暴,听着真是一个帅气的词语,但如果发生在你的系统上那 ...

  4. Java 并发编程CAS、volatile、synchronized原理详解

    CAS(CompareAndSwap) 什么是CAS? 在Java中调用的是Unsafe的如下方法来CAS修改对象int属性的值(借助C来调用CPU底层指令实现的): /*** * @param o ...

  5. 面试准备每日系列:计算机底层之并发编程(二)缓存行、一致性协议、伪共享、disruptor、CAS等待

    文章目录 1. 缓存行 Cache line 2. 缓存一致性协议 & 伪共享 3. 为什么不加volatile? 4. 编程先可用再调优 5. disruptor & CAS等待 1 ...

  6. 面试准备每日系列:计算机底层之并发编程(一)原子性、atomic、CAS、ABA、可见性、有序性、指令重排、volatile、内存屏障、缓存一致性、四核八线程

    文章目录 1. 什么是进程?什么是线程? 2. 线程切换 3. 四核八线程是什么意思 3.1 单核CPU设定多线程是否有意义 4. 并发编程的原子性 4.1 如何解决原子性问题 & atomi ...

  7. 18.AtomicReference、AtomicStampReference底层原理。多个变量更新怎么保证原子性?CAS的ABA问题怎么解决?

    老王:小陈啊,上一章我们说了AtomicInteger.AtomicBoolean的底层原理,这一篇我们就来说说Atomic系列的另一个分类AtomicReference和AtomicStampRef ...

  8. 4.什么是MESI缓存一致性协议?怎么解决并发的可见性问题?

    MESI一致性协议 小陈:老王,上一章你让我看看MESI一致性协议,我大概了解了一下. 老王:哦,来说说你对MESI一致性协议的理解 小陈:MESI协议也叫做缓存一致性协议,主要是用来进行协调多核CP ...

  9. 15.unsafe类的CAS是怎么保证原子性的?

    老王:小陈啊,上一章我们讲了usafe是个啥东西,以及unsafe提供的几大类的功能 老王:这一章啊,我们要花个时间专门讲unsafe提供的cas功能,这个cas的功能是我们后面将Atomic原子类体 ...

最新文章

  1. 常见的http状态码总结。
  2. python条件表达式有哪几个_python条件表达式:多项分支,双向分支
  3. EntityFramework Core进行读写分离最佳实践方式,了解一下(二)?
  4. 【P1063】 能量项链
  5. 测试SqlHelp,linq to SQL,Nhibernate批量处理数据的效率 2009-06-07
  6. leetcode —— 207. 课程表
  7. 开发基于深度学习的人脸识别【考勤/签到】系统
  8. 稳压二极管使用电路图
  9. python爬取qq音乐
  10. WPF实现无线扫码枪无焦点自动获取数据并逻辑处理
  11. 相机标定—— 张正友标定法(2)
  12. 三国志战略版360区S4服务器合并信息,三国志战略版s4赛季开局选哪个州?平民开局起兵地推荐...
  13. docker ps 命令显示格式化和显示完整信息
  14. Linux - vim 文本替换
  15. 内容部分超出出现滚动,隐藏滚动条(还可以滚动)
  16. 容量法和库仑法的异同点_库伦法水分仪和容量法的区别与差异
  17. 51单片机控制步进电机Protues仿真设计
  18. 30岁的测试工程师,青春饭还能吃吗?
  19. 高手常用的自我介绍套路
  20. 重塑股份携手苏州金龙与嘉兴国鸿公交,完成燃料电池客车交付

热门文章

  1. Docker下搭建Redis分片集群
  2. HQChart实战教程41 -新浪+腾讯A股数据源对接 - uniapp版本 (源码付费)
  3. python对配置文件的读写
  4. ubuntu 图片格式批量转换,批量处理
  5. 计算机搜索功能关闭,Win7系统下关闭windows search服务禁用搜索功能的方法
  6. 夏日街头新风尚 | 变色镜片
  7. 【游乐场案例】从实战中看营销思维的应用
  8. 车载雷达(立体摄像头,毫米波雷达,激光雷达(LiDAR))比较
  9. java计算机毕业设计springboot+vue基本微信小程序的电子书阅读器小程序
  10. 设置flex-shrink:0. flex布局只占位,不占空间