首页 > 代码库 > 从Java到CPU指令系列之 - 锁和原子操作是怎么实现的 How Lock and atomic works

从Java到CPU指令系列之 - 锁和原子操作是怎么实现的 How Lock and atomic works

给急性格的读者: 请参考《Intel 64 and IA32 Architectures Software Develeloper‘s Manual》。其中有CMPXCHG指令和LOCK指令前缀。或者AMD等其他厂商的开发指南。

在多线程编程中,对某一资源的同步操作是保证资源状态一致性的关键。这个需要同步的资源可以是单个简单的变量,也可以是多个变量,或者是某些外部资源。对他们同步操作的含义就是同一时间点,最多只能有一个线程在操作这些资源,也就是排他性。并且一系列操作必须一气呵成,中间不允许其他线程做相关的操作,这就是原子性。所以我们在程序中需要让一段代码,在同一时间最多只能有一个线程执行。在Java里我们有Synchronized关键字来标注一段代码在任意时刻只能最多有一个线程在执行。在新的Java Concurrent包里边提供了Lock相关的类。通过Lock我们可以实现与Synchronized同样的效果。这段代码通常我们叫它临界区(Critical Section)。

但是Lock和Synchronize是怎么实现的呢?

从代码上看起来好像在某个地方有个开关。一个线程执行到临界区时,检查这个开关,如果是0代表可以进入,然后把开关设成1,进入,退出,把开关设成0。看起来很完美?其实没有那么简单。因为你这个开关本身可能被多个线程同时检查,它们同时检查到了0,同时进入。。啊 这可不好。再弄个开关?好像陷入无穷无尽的开关也解决不了问题。更天才的想法是,把自己的线程号赋予这个变量,然后等待一小段时间,再检查这个变量是不是自己的进程号,如果是自己,恭喜抢到了。等一小段时间是为了防止其它线程同时检查了,并在你检查之后写了它的线程号。但是,这个等待时间可能很难确定,CPU的线程调度让以上线程间的操作无序且无法知道。看看,这个问题真的貌似不好通过简单的程序诡计解决啊。也许你可以想出更天才的纯软件解决方案,但它可能不那么可靠或者高效。

软件解决不了的问题,其实利用硬件很容易解决。大多CPU都提供了原子操作。请参考开头我提到的文档。CPU保证了某些对单个变量的比较和交换操作是原子的。也就是比较某个数是不是你预期的,如果是,赋予你给的变量,否则不做操作。可以想象CPU在执行此类指令时,可能要暂停多核情况下其它核心的执行,霸占总线,让这个操作不被打断-除非一个CPU周期就可以做完,并且可能要清理CPU的缓存。。。 但是这些还是比我们上边提到的纯软件方法快速且可靠吧!向硬件工程师致敬!

好了,回头看看我们的Java怎么用到CMPXCHG的。

首先进入Java世界。请看ReentrantLock$FairSync.tryAcquire。有没有看到一句话跟我们说的CMPXCHG是同义词?对了,就是compareAndSetState(0, acquires)。一路着下去。。 到了Unsafe.compareAndSwapInt(针对Sun/Oracle Java).这个方法是native的-我们到达了Java世界的边缘。

然后,让我们勇敢的进入C++的世界。请找到OpenJDK的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

是不是找到了atomic.cpp。你看到了很多平台判断的预处理指令。啊,神奇的预处理,C语言实现跨平台的法宝。看起来到此为止,我们也到达了C/C++世界的边缘。

最后,向汇编语言-CPU指令的等价符号-前进。找个比较亲民的平台的把,x86下的atomic_linux_x86.inline.hpp。是的HPP,这里还不是.S。因为C/C++跟汇编的世界交界处不像到Java的世界那样有一堵不透风的墙。汇编可以自由的嵌入C/C++。到此我们终于找到了CPU指令了。

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;
}

 

接下来还有CPU微指令,逻辑电路,与非门。哈哈,只是开个玩笑,我们还是到此打住。向硬件工程师再次致敬!