CAS指令
CAS是CPU的一条指令,其具有原子性,原子性是由CPU硬件层面保证的。
CAS原语有三个操作数——内存位置(V)、预期原值(A)、新值(B)。若内存位置与预期原值匹配则处理器将该位置更新为新值。否则不做操作。无论何种情况都会在CAS指令之前返回该位置值。这个过程是原子性的。
底层硬件通过将 CAS 里的多个操作在硬件层面语义实现上,通过一条处理器指令保证了原子性操作。这些指令如下所示:
(1)测试并设置(Tetst-and-Set)
(2)获取并增加(Fetch-and-Increment)
(3)交换(Swap)
(4)比较并交换(Compare-and-Swap)
(5)加载链接/条件存储(Load-Linked/Store-Conditional)
前面三条大部分处理器已经实现,后面的两条是现代处理器当中新增加的。而且根据不同的体系结构,指令存在着明显差异。
在IA64,x86 指令集中有 cmpxchg 指令完成 CAS 功能。
sun.misc.Unsafe 中 CAS 的核心方法:
这里的实现和CAS原语基本一致,唯一的区别就是返回类型不一样,CAS指令返回内存地址最新值,这里成功写入返回true,失败返回false
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5); public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5); public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
这三个方法可以对应去查看 openjdk 的 hotspot 源码:
源码位置:hotspot/src/share/vm/prims/unsafe.cpp
#define FN_PTR(f) CAST_FROM_FN_PTR(void*, &f) {CC"compareAndSwapObject", CC"("OBJ"J"OBJ""OBJ")Z", FN_PTR(Unsafe_CompareAndSwapObject)}, {CC"compareAndSwapInt", CC"("OBJ"J""I""I"")Z", FN_PTR(Unsafe_CompareAndSwapInt)}, {CC"compareAndSwapLong", CC"("OBJ"J""J""J"")Z", FN_PTR(Unsafe_CompareAndSwapLong)},
三个方法,最终在 hotspot 源码实现中都会调用统一的 cmpxchg 函数,可以在 hotspot 源码中找到核心代码。
源码地址:hotspot/src/share/vm/runtime/Atomic.cpp
cmpxchg 函数源码:可以看到调用了cmpxchg汇编指令执行CAS操作
1 jbyte Atomic::cmpxchg(jbyte exchange_value, volatile jbyte*dest, jbyte compare_value) { 2 assert (sizeof(jbyte) == 1,"assumption."); 3 uintptr_t dest_addr = (uintptr_t) dest; 4 uintptr_t offset = dest_addr % sizeof(jint); 5 volatile jint*dest_int = ( volatile jint*)(dest_addr - offset); 6 // 对象当前值 7 jint cur = *dest_int; 8 // 当前值cur的地址 9 jbyte * cur_as_bytes = (jbyte *) ( & cur); 10 // new_val地址 11 jint new_val = cur; 12 jbyte * new_val_as_bytes = (jbyte *) ( & new_val); 13 // new_val存exchange_value,后面修改则直接从new_val中取值 14 new_val_as_bytes[offset] = exchange_value; 15 // 比较当前值与期望值,如果相同则更新,不同则直接返回 16 while (cur_as_bytes[offset] == compare_value) { 17 // 调用汇编指令cmpxchg执行CAS操作,期望值为cur,更新值为new_val 18 jint res = cmpxchg(new_val, dest_int, cur); 19 if (res == cur) break; 20 cur = res; 21 new_val = cur; 22 new_val_as_bytes[offset] = exchange_value; 23 } 24 // 返回当前值 25 return cur_as_bytes[offset]; 26 }
ABA问题
CAS操作是判断当前内存中的值与期望值是否相等,相等才更新。但如果第一个线程执行CAS操作,其期望值是对象A,其它线程在这之前吧内存中的值改为了B,又改为了A。这时会判断相等,但实际A的属性可能已经改变,判断条件已经发生了变化,这就产生了线程安全的问题。
解决方法:
添加版本号,每次更新内存操作时版本号加1,比较原值的同时比较版本号。
AtomicStampReference
AtomicStampReference在cas的基础上增加了一个标记stamp,使用pair将原数据包裹起来,基于pair进行底层cas操作。
1 // 类的定义: 2 public class AtomicStampedReference<V> 3 // 构造函数,将对象和标记值传入 4 public AtomicStampedReference(V initialRef, int initialStamp) { 5 pair = Pair.of(initialRef, initialStamp); 6 } 7 // 参数代表的含义分别是 期望值,写入的新值,期望标记,新标记值 8 public boolean compareAndSet(V expectedReference, 9 V newReference, 10 int expectedStamp, 11 int newStamp) { 12 Pair<V> current = pair; 13 // 比较原对象的同时比较版本号是否也相同,如果都相同则进行pair的cas操作 14 return 15 expectedReference == current.reference && 16 expectedStamp == current.stamp && 17 ((newReference == current.reference && 18 newStamp == current.stamp) || 19 casPair(current, Pair.of(newReference, newStamp))); 20 } 21 // 如果在这之前已经有线程对pair进行更新,则会执行失败 22 private boolean casPair(Pair<V> cmp, Pair<V> val) { 23 return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val); 24 } 25 public V getRerference(); 26 public int getStamp(); 27 public void set(V newReference,int newStamp);