编程语言与高级语言虚拟机杂谈仮 2018-05-27
从Java到c++到汇编, 深入讲解cas的底层原理.
以AtomicBoolean类为例.先来一个调用cas的demo.
主线程在for语句里cas忙循环, 直到cas操作成功返回true为止.
而新开的一个县城new Thread 会在4秒后,将flag设置为true, 为了让主线程能够设置成功.(因为cas的预期值是true, 而flag被初始化为了false)
现象就是主线程一直在跑for循环. 4秒后, 主线程将会设置成功, 然后输出时间差, 然后终止for循环.
public class TestAtomicBoolean { public static void main(String[] args) { AtomicBoolean flag = new AtomicBoolean(false); long start = System.currentTimeMillis(); new Thread(()->{ try { Thread.sleep(4000); } catch (InterruptedException e) { e.printStackTrace(); } flag.set(true); }).start(); for(;;){ if(flag.compareAndSet(true,false)){ System.out.println(System.currentTimeMillis() - start); System.out.println("inner loop OK!"); break; } } } }
这里只是举了一个例子, 也许这个例子也不太恰当, 本文只是列出了这个api的调用方法而已, 重点在于介绍compareAndSet()方法的底层原理.
发现AtomicBoolean的compareAndSet()调用的是unsafe里的compareAndSwapInt()方法.
有的同学可能好奇, 其中的unsafe是怎么来的.
在AtomicBoolean类中的静态成员变量:
如果还要细究Unsafe.getUnsafe()是怎么实现的话....那么我再贴一份Unsafe类里的getUnsafe的代码:
首先, 在Unsafe类里, 自己就有了一个自己的实例.(单例)
然后Unsafe类里的getUnsafe()方法会进行检查, 最终会return这个单例 theUnsafe.
刚刚跑去取介绍了getUnsafe()方法...接下来继续讲解cas...
刚才说到了AtomicBoolean类里的compareAndSet()方法内部其实调用了Unsafe类里的compareAndSwapInt()方法.
Unsafe类里的compareAndSwapInt源码如下:
(OpenJDK8的源码里路径: openjdk/jdk/src/share/classes/sun/misc/Unsafe.java)
发现这里是一段native方法.说明继续看源码的话, 从这里就开始脱离Java语言了....
本源码在OpenJDK8里的路径为: openjdk/hotspot/src/share/vm/prims/unsafe.cpp
(这里临时跑题一下: 如果说要细究 UNSAFE_ENTRY 是什么的话...UNSAFE_ENTRY 就是JVM_ENTRY, 而JVM_ENTRY 在interfaceSupport.hpp里面定义了, jni相关.如果想看的话, 源码路径在OpenJDK8中的路径是这个:
openjdk/hotspot/src/share/vm/runtime/interfaceSupport.hpp)
回到本文的主题cas....上面截图的这段代码, 最后一句是return, 发现其中的使用到的是Atomic下的cmpxchg()方法.
本段源码对应OpenJDK8的路径是这个:openjdk/hotspot/src/share/vm/runtime/atomic.cpp
其中的cmpxchg为核心内容. 但是这句代码根据操作系统和处理器的不同, 使用不同的底层代码.
而atomic.inline.hpp里声明如下:
可见 ...不同不同操作系统, 不同的处理器, 都要走不同的cmpxchg()方法的实现.
咱们接下来以其中的linux操作系统 x86处理器为例 , atomic_linux_x86.inline.hpp
OpenJDK中路径如下: openjdk/hotspot/src/os_cpu/linux_x86/vm/atomic_linux_x86.inline.hpp
看到了__asm__, 说明c++要开始内联汇编了,说明继续看代码的话, 将会是汇编语言.
这是一段内联汇编: