Java中CAS(compare and swap)相关方法,是通过Unsafe
中的native
方法向下调用底层操作系统,Java语言层面只是对其封装、暴露;
CAS由CPU层面的指令(CMPXCHG指令)去实现,CAS是实现锁和非阻塞并发数据结构的基础同步原语。
这里建议先了解CPU缓存和MESI这两个知识点(MESI协议 - 维基百科、计算机组成原理_CPU缓存)。
1. 结论先行
CAS(compare and swap)是CPU层面实现的,在Intel的CPU中由CMPXCHG指令来实现;
多核CPU中,通过将CMPXCHG
与LOCK
前缀结合起来,实现以原子方式执行比较和交换操作。
2. MESI
先炒炒冷饭,回顾下MESI。
现代CPU中有多个core,每个core都有自己的L1、L2缓存,那就会遇到缓存一致性的问题:
同一数据的多个副本可能同时存在于不同core的缓存中。如果允许CPU自由地修改它们自己的副本,就会导致不同core的缓存对存储器中同一数据的反映不一致。
现代计算机、操作系统通过MESI协议来确保缓存一致性。
MESI是cache line(缓存行)
的四种状态:Modified(已修改)
、Exclusive(独占)
、Shared(共享)
、Invalid(失效)
;多核系统中,缓存行都处于这四种状态之一。
独占(Exclusive)缓存行
和主存保持一致,并且缓存行只在一个core中(其他core不能同时持有它);
当别的core要读取它时,状态变为共享
;当core写(修改)后,状态变为已修改(Modified)
。在
独占(Exclusive)
状态下的缓存行,如果收到来自总线的读取请求,这个缓存行就会变成共享(Shared)
状态。这个共享状态是因为,另一个core也把对应的缓存行从内存中加载到了自己的缓存中。已修改(Modified)缓存行
缓存行中数据与主存中的数据不同,被所属的core修改;如果别的CPU内核要读主存这块数据,该缓存行必须回写到主存,状态变为共享。core的写操作只能在缓存行状态是
已修改
或独占
时可以自由执行(如果没有独占,同一份数据存在于多个core的缓存中,不能直接修改)。如果在缓存行是
共享
状态,core要先发送一条"我要独占"的请求给总线,总线会通知其他所有core(向其他所有的core广播一个请求),要求其他core先把它们对应缓存中的缓存行标识为失效
状态(如果它们有的话);这个广播动作叫做RFO(Request For Ownership),即获取当前对应缓存行数据的所有权。
只有获得独占权后,core才能开始修改数据,并且此时core知道,这个缓存行只有一份拷贝,就在自己的缓存中,core写完以后,将状态置为
已修改
。上面说过,core会一直嗅探总线,监听读写请求。
对于
已修改
状态的缓存行,如果别的core要读主存这块数据,该缓存行必须插入总线、回写到主存,状态变为共享
。失效(Invalid)缓存行
这种状态的缓存行会被忽略; 一旦缓存行被标识为’失效’,那对于CPU来说,等同于它从来没被加载到缓存中。共享(Shared)缓存行
和主存数据保持一致的拷贝,这种状态下的缓存行可以在任意时刻抛弃。上面说过,core会一直嗅探总线,监听读写请求。
对于共享状态的缓存行,如果其他core有
独占
广播,并且命中,则会将该缓存行状态置为失效
状态
3. CAS
几乎现代所有CPU指令都支持CAS原子操作,在Intel#x86的CPU架构中,通过cmpxchg
指令集完成CAS(compare and swap)功能;如果CPU是多核,必须添加lock前缀;
截止2013年,大多数处理器架构在硬件上都支持CAS,**CAS(compare and swap)**是实现锁和非阻塞并发数据结构的基础同步原语。
---- Compare-and-swap - Wikipedia
CAS操作包含三个操作数:内存位置、预期值、新值。
CAS的实现逻辑是:将内存位置处的数值与预期值进行比较,如果相等,则将内存位置处的值替换为新值;或不等,则不做任何操作。
需要注意的是,CAS的实现原理依赖于MESI
协议,直白的说:多个Core同时执行针对同一地址的CAS指令时,其实它们是在试图修改(独占)Core持有的cache line(缓存行)
。
假设两个Core(A#Core和B#Core)都持有相同地址对应的cache line(缓存行)
,并且它们各自的状态都是Shared(共享)
;
如果此时A#Core想要成功修改,首先要把缓存行的状态由Shared(共享)
改为Exclusive(独占)
。
即:A#Core向总线广播
Exclusive(独占)
状态请求,获取当前对应缓存行数据的所有权,B#Core嗅探到Exclusive(独占)
请求,会将自己Core所属的cache line(缓存行)
状态设置为失效(Invalid)
;
但如果此时A#Core和B#Core同时向总线广播Exclusive(独占)
状态请求,那就会触发总线仲裁,看A#Core和B#Core谁能获胜,获胜者就能获取cache line(缓存行)
的独占;失败者则将自己Core所属的cache line(缓存行)
状态设置为失效(Invalid)
;
以上分析,基于
cache line
(缓存行)对齐的情况下,如果cache line(缓存行)
没有对齐,则会触发lock bus(总线锁)
。--- 《Intel64 and IA32 Architectures Software Developer’s Manual》(Chapter 8.1.4 Effects of a LOCK Operation on Internal Processor Caches)
3.1. CMPXCHG指令
CMPXCHG(比较与交换) 和 CMPXCHG8B(比较与交换 8bytes) 指令在多处理器系统中用于同步操作。
CMPXCHG
指令需要三个操作数:寄存器中的一个源操作数、EAX寄存器中的另一个源操作数和目标操作数。如果目标操作数和EAX寄存器中包含的值相等,则目标操作数将替换为另一个源操作数的值(不在EAX寄存器中的值)。否则,目标操作数的原始值被加载到EAX寄存器中。EFLAGS寄存器中的状态标志反映了从EAX寄存器中的值减去目标操作数所获得的结果。
CMPXCHG
指令通常用于测试和修改信号量;它检查信号量是否空闲。
如果信号量是空闲的,则将其标记为已分配;如果信号量不是空闲的,将获取到当前所有者的ID;这一切都是在一个不间断的操作中完成的;
在单处理器系统中,CMPXCHG
指令在执行多条指令测试和修改信号量之前,不需要切换到保护级别0(禁用中断)。
对于多处理器系统,可以将CMPXCHG
与LOCK
前缀结合起来,以原子方式执行比较和交换操作。
The CMPXCHG (compare and exchange) and CMPXCHG8B (compare and exchange 8 bytes) instructions are used to synchronize operations in systems that use multiple processors.
The CMPXCHG instruction requires three operands: a source operand in a register, another source operand in the EAX register, and a destination operand. If the values contained in the destination operand and the EAX register are equal, the destination operand is replaced with the value of the other source operand (the value not in the EAX register). Otherwise, the original value of the destination operand is loaded in the EAX register. The status flags in the EFLAGS register reflect the result that would have been obtained by subtracting the destination operand from the value in the EAX register.
The CMPXCHG instruction is commonly used for testing and modifying semaphores. It checks to see if a semaphore is free.
If the semaphore is free, it is marked allocated; otherwise it gets the ID of the current owner. This is all done in one uninterruptible operation.
In a single-processor system, the CMPXCHG instruction eliminates the need to switch to protection level 0 (to disable interrupts) before executing multiple instructions to test and modify a semaphore.
For multiple processor systems, CMPXCHG can be combined with the LOCK prefix to perform the compare and exchange operation atomically.
4. Reference
- 《Intel64 and IA32 Architectures Software Developer’s Manual》
- Compare-and-swap - Wikipedia
- 浅论Lock 与X86 Cache 一致性