赞
踩
JDK 文档对 compareAndSet() 方法说明:如果当前状态值等于预期值,则以原子方式将同步状态 设置为给定的更新值。此操作具有 volatile 读和写的内存语义。
1.以 Unsafe#compareAndSetInt 方法为例进行说明
@HotSpotIntrinsicCandidate
public final native boolean compareAndSwapInt(Object o, long offset,int expected,int x);
2.方法解读:
volatile int value
的字段)3.实际使用示例(AtomicInteger#compareAndSet):
public class AtomicInteger extends Number implements java.io.Serializable { // Unsafe 类定义 private static final Unsafe unsafe = Unsafe.getUnsafe(); // 获取变量 value 在内存中对应的地址偏移量 private static final long valueOffset; static { try { valueOffset = unsafe.objectFieldOffset // 内存中对应的地址偏移量获取 (AtomicInteger.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } } private volatile int value; // expectedValue 预期值、newValue 修改值 public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); }
分析 Java 中Unsafe#compareAndSwapInt
方法。
native 方法最终调用实现hotspot/src/os_cpu/linux_x86/vm/atomic_linux_x86.inline.hpp
中Atomic::cmpxchg
实现
inline jint Atomic::cmpxchg(jint exchange_value, volatile jint* dest, jint compare_value) {
int mp = os::is_MP();
__asm__ volatile (LOCK_IF_MP(%4) "cmpxchgl %1,(%3)"
: "=a" (exchange_value)
: "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp)
: "cc", "memory");
return exchange_value;
}
程序会根据当前处理器的类型来决定是否为 cmpxchg 指令添加 lock 前缀。
intel 的手册对 lock 前缀的说明如下:
上面的第 2 点和第 3 点所具有的内存屏障效果,足以同时实现 volatile 读和 volatile 写的内存语义。
经过上面的分析,现在我们终于能明白为什么 JDK 文档说 CAS 同时具有 volatile 读和 volatile 写的内存语义了。
因为 CAS 需要在操作值的时候,检查值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是 A,变成了 B,又变成了 A,那么使用 CAS 进行检查时会发现它的值没有发生变化,但是实际上却变化了。
ABA 问题的解决思路就是使用版本号。在变量前面 追加上版本号,每次变量更新的时候把版本号加 1,那么 A→B→A 就会变成 1A→2B→3A。从 Java 1.5 开始,JDK 的 Atomic 包里提供了一个类 AtomicStampedReference 来解决 ABA 问题。 这个类的 compareAndSet 方法的作用是首先检查当前引用是否等于预期引用,并且检查当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。
自旋 CAS 如果长时间不成功,会给 CPU 带来非常大的执行开销。
如果 JVM 能支持处理器提供的 pause 指令,那么效率会有一定的提升。 pause 指令有两个作用:
- 它可以延迟流水线执行指令 (de-pipeline),使 CPU 不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零;
- 它可以避免在退出循环的时候因内存顺序冲突 (Memory Order Violation) 而引起 CPU 流水线被清空 (CPU Pipeline Flush),从而提高 CPU 的执行效率。
当对一个共享变量执行操作时,我们可以使用循环 CAS 的方式来保证原子操作,但是对多个共享变量操作时,循环 CAS 就无法保证操作的原子性,这个时候就可以用锁。
还有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比如,有两个共享变量 i=2,j=a,合并一下 ij=2a,然后用 CAS 来操作 ij。从 Java 1.5 开始,JDK 提供了 AtomicReference 类来保证引用对象之间的原子性,就可以把多个变量放在一个对象里来进行 CAS 操作。
CAS 实现原子操作的三大问题
,保证存在问题与需求不会冲突volatile
是否满足需求 ?Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。