再次设置AtomicBoolean

时间:2018-11-22 16:26:07

标签: java multithreading caching concurrency volatile

我正在使用AtomicBoolean来增强线程之间的volatile可见性。一个线程正在更新值,另一个线程仅读取它。

说当前值为true。现在说一个写入线程再次将其值设置为true

final AtomicBoolean b = new AtomicBoolean(); // shared between threads

b.set(true);
// ... some time later
b.set(true);

在此“虚拟” set(true)之后,当 read thread 调用get()时,性能会受到损失吗? 读取线程是否必须重新读取并缓存该值?

在这种情况下,写入线程可能已经完成:

b.compareAndSet(false, true);

通过这种方式,读取线程只需使实际更改无效。

2 个答案:

答案 0 :(得分:3)

compareAndSet()

public final boolean compareAndSet(boolean expect, boolean update) {
    int e = expect ? 1 : 0;
    int u = update ? 1 : 0;
    return unsafe.compareAndSwapInt(this, valueOffset, e, u);
}

compareAndSwapInt()已经是本地用户了:

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

在JVM执行开始时Atomic::cmpxchggenerated的某个位置,

  address generate_atomic_cmpxchg() {
    StubCodeMark mark(this, "StubRoutines", "atomic_cmpxchg");
    address start = __ pc();

    __ movl(rax, c_rarg2);
   if ( os::is_MP() ) __ lock();
    __ cmpxchgl(c_rarg0, Address(c_rarg1, 0));
    __ ret(0);

    return start;
  }

cmpxchgl()生成x86代码(它也具有更长的传统代码路径,因此我不在此处复制该代码):

 InstructionMark im(this);
 prefix(adr, reg);
 emit_byte(0x0F);
 emit_byte(0xB1);
 emit_operand(reg, adr);

0F B1实际上是一个CMPXCHG操作。如果您检查上面的代码,if ( os::is_MP() ) __ lock();在多处理器机器上发出一个LOCK前缀(让我跳过引号lock(),它发出一个F0字节),因此几乎到处都是

正如CMPXCHG文档所说:

  

此指令可以与LOCK前缀一起使用,以允许原子执行该指令。为了简化与处理器总线的接口,目标操作数接收一个写周期,而不考虑比较结果。如果比较失败,则写回目标操作数;否则,将写回目标操作数。否则,将源操作数写入目标。 (处理器从不产生锁定的读取,而不产生锁定的写入。

因此,在多处理器x86机器上,NOP-CAS也会执行写操作,从而影响高速缓存行。 (强调由我添加)

答案 1 :(得分:1)

写操作和CAS“触摸”高速缓存行都会触发高速缓存行变脏。

但是成本相对较小,大约为30-50 ns。

由于尚未运行10,000次而未预热代码的成本可能会更高。