并发专题
- 深入解析JVM-类加载机制
- 深入解析JVM-Java对象头组成
- 深入JVM内置锁 synchronized 底层
- 深入理解JMM-Java内存模型
- 深入理解JMM-volatile原理分析
- 并发编程-深入解析CAS
- 并发编程-深入AQS原理
CAS 无锁机制
- CAS: Compare and Swap,翻译成比较并交换
- CAS是一种无锁算法,在不使用锁的情况下实现多线程之间的变量同步
- CAS 是通过硬件指令,保证原子性
- CAS 有三个操作数:内存值V,旧的预期值E,要修改的新值N
- 仅当预期值E和内存值V相同时,将内存值V修改为N
- V为内存值 (共享变量),E为旧预值(读取的V的副本值、工作内存值),N为新值为可以修改的值
CAS应用
CAS 方法说明
- CAS 操作是由 Unsafe 类提供支持的 native方法
- CAS 方法接收 4 个参数,分别是:对象实例、内存偏移量、字段期望值、字段新值
@HotSpotIntrinsicCandidate
public final native boolean compareAndSetObject(Object o, long offset,
Object expected,
Object x);
@HotSpotIntrinsicCandidate
public final native boolean compareAndSetInt(Object o, long offset,
int expected,
int x);
@HotSpotIntrinsicCandidate
public final native boolean compareAndSetLong(Object o, long offset,
long expected,
long x);
示例
public static Unsafe getUnsafe() {
try {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
return (Unsafe) field.get(null);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static long getFieldOffset(Unsafe unsafe, Class clazz, String fieldName) {
try {
return unsafe.objectFieldOffset(clazz.getDeclaredField(fieldName));
} catch (NoSuchFieldException e) {
throw new Error(e);
}
}
@Test
public void casTest(){
Unsafe unsafe = getUnsafe();
// 对象实例
ServerInfo serverInfo = new ServerInfo();
long offset = getFieldOffset(unsafe, ServerInfo.class, "token");
// compareAndSwapInt(Object var1, long var2, int var4, int var5)
// 对象实例、字段的内存偏移量、字段期望值、字段新值
boolean result = unsafe.compareAndSwapInt(serverInfo, offset, 2, 4);
}
CAS 源码分析
Hotspot 虚拟机对compareAndSwapInt 方法的实现如下:
#unsafe.cpp
UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
UnsafeWrapper("Unsafe_CompareAndSwapInt");
oop p = JNIHandles::resolve(obj);
// 根据偏移量,计算value的地址
jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
// Atomic::cmpxchg(x, addr, e) cas逻辑 x:要交换的值 e:要比较的值
//cas成功,返回期望值e,等于e,此方法返回true
//cas失败,返回内存中的value值,不等于e,此方法返回false
return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
核心逻辑在Atomic::cmpxchg方法中,这个根据不同操作系统和不同CPU会有不同的实现。这里我们以linux_64x的为例,查看Atomic::cmpxchg的实现
#atomic_linux_x86.inline.hpp
inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) {
//判断当前执行环境是否为多处理器环境
int mp = os::is_MP();
//LOCK_IF_MP(%4) 在多处理器环境下,为 cmpxchgl 指令添加 lock 前缀,以达到内存屏障的效果
//cmpxchgl 指令是包含在 x86 架构及 IA-64 架构中的一个原子条件指令,
//它会首先比较 dest 指针指向的内存值是否和 compare_value 的值相等,
//如果相等,则双向交换 dest 与 exchange_value,否则就单方面地将 dest 指向的内存值交给exchange_value。
//这条指令完成了整个 CAS 操作,因此它也被称为 CAS 指令。
__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;
- Atomic::cmpxchg这个函数最终返回值是exchange_value,也就是说,如果cmpxchgl执行时compare_value和dest指针指向内存值相等则会使得dest指针指向内存值变成exchange_value,最终eax存的compare_value赋值给了exchange_value变量,即函数最终返回的值是原先的compare_value。
- Unsafe_CompareAndSwapInt的返回值(jint)(Atomic::cmpxchg(x, addr, e)) == e就是true,表明CAS成功
CAS 优缺点
- 优点:CAS无锁机制,避免用户态到内核态切换
- 缺点:1、CAS 长时间自旋会消耗CPU资源,开销大 2、ABA问题
ABA问题
如果将原来的值A,改为了B,B有改为了A 发现没有发生变化,实际上已经发生了变化,存在了ABA问题
解决方案:通过版本号码,对每个变量更新的版本就会进行累加,使用原子引用类AtomicStampedReference
public class AtomicStampedReference<V> {
private static class Pair<T> {
// 当前值
final T reference;
// 版本
final int stamp;
private Pair(T reference, int stamp) {
this.reference = reference;
this.stamp = stamp;
}
static <T> Pair<T> of(T reference, int stamp) {
return new Pair<T>(reference, stamp);
}
}
private volatile Pair<V> pair;
...
}
reference即我们实际存储的变量,stamp是版本,每次修改可以通过+1保证版本唯一性
@Slf4j
public class AtomicStampedReferenceTest {
private final static AtomicStampedReference<Integer> stamp = new AtomicStampedReference<>(100, 1);
public static void main(String[] args) {
new Thread(() -> {
log.info(Thread.currentThread().getName() + " 第1次版本号:" + stamp.getStamp());
stamp.compareAndSet(100, 200, stamp.getStamp(), stamp.getStamp() + 1);
log.info(Thread.currentThread().getName() + " 第2次版本号:" + stamp.getStamp());
stamp.compareAndSet(200, 100, stamp.getStamp(), stamp.getStamp() + 1);
log.info(Thread.currentThread().getName() + " 第2次版本号:" + stamp.getStamp());
}).start();
}
}