本文前置知识: CAS算法原理和ABA问题
之前提到,CAS的原理就是 在 将数据写入主内存的时候,将主内存的值与第一次读取到的值进行比较,如果两个值相同,则执行更新,否则,就继续循环。
那么细心的人可能会思考,如果在 比较 -> 更新 这两个过程之间,有别的线程修改了主内存的数据,那么不是一样会出现并发问题吗?
答案是,并不会出现并发问题。 原因是,CAS的比较和更新这两个操作是原子性的,中间不会被别的线程打断。
那么本篇文章来具体探讨一下,为什么CAS的 比较和更新 这两个步骤是原子性的。
我们还以AtomicInteger为例,从之前的文章,我们知道该类对数据的操作,底层依赖了CAS算法。 现在我们再去看一下。
首先看一下AtomicInteger类的getAndIncrement()方法, 该方法可以实现并发下对数据的自增操作。
public final int getAndIncrement() {return unsafe.getAndAddInt(this, valueOffset, 1);
}
我们看到该方法中调用了UnSafe类的getAndAddInt()方法, 我们去看一下。
public final int getAndAddInt(Object var1, long var2, int var4) {int var5;do {var5 = this.getIntVolatile(var1, var2);} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));return var5;
}
该方法使用while循环的方式调用了compareAndSwapInt()方法,通过方法名,可以得知这个方法即是使用CAS的方式为变量进行赋值。
如果CAS在写入之前比较两个值不相等,那么就执行while循环,直到成功为止。这也就是我们所说的自旋锁了。
那么compareAndSwapInt()这个方法又是怎么实现的呢?
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
我们看到,UnSafe类中的compareAndSwapInt()方法是一个 native方法,调用的是底层由c 或 c++ 编写的代码。
到这里我们需要查看Hotspot源码。源码下载地址
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);jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);return (jint)(Atomic::cmpxchg(x, addr, e)) == e;UNSAFE_END
cmpxchg = compare and exchange 这个指令是比较并交换指令,我们继续看调用链。
atomic_linux_x86.inline.hpp 93行
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;}
is_MP = Multi Processor
os.hpp is_MP()
static inline bool is_MP() {return (_processor_count != 1) || AssumeMP;}
atomic_linux_x86.inline.hpp
#define LOCK_IF_MP(mp) "cmp $0, " #mp "; je 1f; lock; 1: "
通过对Hotspot源码的调用链跟踪,最终得出CAS底层的实现方式:
lock cmpxchg 指令
这里的 cmpxchg指令 = cas修改变量值
lock指令的作用: 执行后面cmpxchg指令的时候锁定一个北桥信号 (不采用锁总线的方式),这个lock指令的效率要远高于JDK中实现的锁。