我正在使用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);
通过这种方式,读取线程只需使实际更改无效。
答案 0 :(得分:3)
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::cmpxchg
在generated的某个位置,
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次而未预热代码的成本可能会更高。