torutkのブログ

ソフトウェア・エンジニアのブログ

本日はJava読書会「Java Concurrency in Practice」でした

read-modify-writeをアトミックに実行できるAtomicオブジェクトの実装について話題にのぼったので、Java SE 6(Mustang)のソースを調査してみることにしました。
書籍で登場したAtomicLongの使い方は

  private final AtomicLong count = new AtomicLong(0);
    :
    count.incrementAndGet();

java.util.concurrent.atomic.AtomicLongクラスの実装調査

まずは、incrementAndGetメソッドの中身を見ます。

  public final long incrementAndGet() {
    for(;;) {
      long current = get();
      long next = current + 1;
      if (compareAndSet(current, next))
        return next
    }
  }

というコードで、アトミックなcheck-then-act操作を持つ(はずの)compareAndSetメソッドを成功するまで繰り返し実行しています。


続いて、compareAndSetメソッドの中身を見ます。

public final boolean compareAndSet(long expect, long update) {
  return unsafe.compareAndSwapLong(this, valueOffset, expect, update);
}

ここを見ると、unsafeというオブジェクトのcompareAndSwapLongを呼び出しています。unsafeは、sun.misc.Unsafeクラスで、そのcompareAndSwapLongメソッドはnativeとして宣言されています。

nativeメソッドcompareAndSwapLongの調査

Mustangソースコードj2se/src/以下をgrepしてもcompareAndSwapLongの実装が見当たりません。さらに探すと、hotspot/src/share/vm/prims/unsafe.cppにらしきコード断片を見つけました。

UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapLong(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jlong e, jlong x))
  :
  if (VM_Version::supports_cx8())
    return (jlong)(Atomic::cmpxchg(x, addr, e)) == e;
  else {
    jboolean success = false;
    ObjectLocker ol(p, THREAD);
    if (*addr == e) { *addr = x; success = true; }
    return success;
}

次は、Atomic::cmpxchg関数を検索すると、Windows OS(32bit)の場合、
hotspot/src/os_cpu/win32_i486/vm/atomic_win32_i486.inline.hppにコードがありました。

inline jlong Atomic::cmpxchg (jlong exchange_value, volatile jlong* dest, jlong compare_value) {
  :
  __asm {
    :
    cmpxcg8b qword ptr [edi]
    :
  }
}

Solaris SPARCの場合、hotspot/src/os_cpu/solaris_sparc/vm/atomic_solaris_sparc.inline.hppにコードがあります。

inline jlong Atomic::cmpxchg (jlong exchange_value, volatile jlong* dest, jlong compare_value) {
#ifdef LP64
  return _Atomic_cas64((intptr_t)exchange_value, (intptr_t*)dest, (intptr_t)compare_value);
#else
  return _Atomic_casl(exchange_value, dest, compare_value);
#endif
}

64bitと32bitとで処理が異なりますが、これらはどちらもインラインアセンブリで書かれた処理を呼び出しています。

solaris_sparc.il

.inline _Atomic_cas64, 3
.volatile
casx    [%o1], %o2, %o0
.nonvolatile
.end

.inline _Atomic_casl, 3
.volatile
sllx    %o0, 32, %o0
srl     %o1, 0, %o1
or      %o0, %o1, %o0
sllx    %o3, 32, %o3
srl     %o4, 0, %o4
or      %o3, %o4, %o3[]
casx    [%o2], %o3, %o0
srl     %o0, 0, %o1
srlx    %o0, 32, %o0
.nonvolatile
.end