CAS:全称Compare and swap,字面意思:"比较并交换",一个CAS涉及到以下操作
我们假设内存中的原数据V,旧的预期值A,需要修改的新值B。1.比较A与V是否相等。
(比较)2.如果比较相等,将B写入V。(交换)3.返回操作是否成功。
当多个线程同时对某个资源进行CAS操作,只能有一个线程操作成功,但是并不会阻塞其他线 程,其他线程只会收到操作失败的信号。可见CAS其实是一个乐观锁。
CAS是怎么实现的
跟随Atominteger的代码我们一路往下,就能发现最终调用的是sum.misc.Unsafe这个类。看 名称Unsafe就是一个不安全的类,这个类是利用了 Java的类和包在可见性的的规则中的一 个恰到好处处的漏洞。Unsafe这个类为了速度,在Java的安全标准上做岀了 一定的妥协。
再往下寻找我们发现Unsafe的compareAndSwapInt是Native的方法:
public final native boolean compareAndSwapInt(Object varl, long var2, int var4, int var5);
也就是说,这几个CAS的方法应该是使用了本地的方法。所以这几个方法的具体实现需要我们自己去jdk的源码中搜索。
于是我下载一个OpenJdk的源码继续向下探索,我们发现
在 /jdk9u/hotspot/src/share/vm/unsafe.cpp中有这样的代码:
{CC "compareAndSetlnt", CC " (" OBJFN_PTR(Unsafe_CompareAndSetInt)},
这个涉及到,JNI的调用,感兴趣的同学可以自行学习。我们搜索Unsafe_CompareAndSetInt 后发现:
UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSetInt (JNIEnv env, jobject unsafe, jobject obj, jlong offset, jint e, jint x)) {
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
最终我们终于看到了核心代码Atomic:: cmpxchg。
继续向底层探索,在文件
java/jdk9u/hotspot/src/os_cpu/linux_x86/vm/atomic_linux_x86.hpp 有这样的代码:
inline jint Atomic::cmpxchg (jintexchange_value, volatile jint dest, jint
compare_value, cmpxchg_memory_order order) {
int mp = os:: is_MP();
asm volati.le (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 ;
}
我们通过文件名可以知道,针对不同的操作系统,JVM对于Atomic::cmpxchg应该有不同的实 现。由于我们服务基本都是使用的是64位linux,所以我们就看看linux_x86的实现。
我们继续看代码:
1、 _asm_的意思是这个是一段内嵌汇编代码。也就是在C语言中使用汇编代码。
2、 这里的volatile和JAVA有一点类似,但不是为了内存的可见性,而是告诉编译器对访 问该变量的代码就不再进行优化。
3、 LOCK_IF_MP(%4)的意思就比较简单,就是如果操作系统是多线程的,那就增加一个LOCK。
4、 cmpxchgl就是汇编版的“比较并交换”。但是我们知道比较并交换,有三个步骤,不是 原子的。所以在多核情况下加一个LOCK,由CPU硬件保证他的原子性。
5,我们再看看LOCK是怎么实现的呢?我们去Intel的官网上看看,可以知道LOCK在的早 期实现是直接将cup的总线阻塞,这样的实现可见效率是很低下的。后来优化为X86 cpu有 锁定一个特定内存地址的能力,当这个特定内存地址被锁定后,它就可以阻止其他的系统总线 读取或修改这个内存地址。
关于CAS的底层探索我们就到此为止。我们总结一下JAVA的cas是怎么实现的:
1、 java的cas利用的的是unsafe这个类提供的cas操作。
2、 unsafe的cas依赖了的是jvm针对不同的操作系统实现的Atomic::cmpxchg
3、 Atomic:: cmpxchg的实现使用了汇编的cas操作,并使用cpu硬件提供的lock信号保 证其原子性
CAS的使用能够避免线程的阻塞。
多数情况下我们使用的是while true直到成功为止。
CAS缺点
1、 ABA的问题,就是一个值从A变成了 B又变成了 A,使用CAS操作不能发现这个值发生变 化了,处理方式是可以使用携带类似时间戳的版本AtomicStampedReference
2,性能问题,我们使用时大部分时间使用的是while true方式对数据的修改,直到成功为 止。优势就是相应极快,但当线程数不停增加时,性能下降明显,因为每个线程都需要执行,占用CPU时间。
Was this helpful?
0 / 0