我正在使用一个AtomicBoolean来执行线程之间的volatile可见性。一个线程正在更新该值,另一个线程只在读取它。
假设当前值是true。现在假设写线程再次将其值设置为true:
final AtomicBoolean b = new AtomicBoolean(); // shared between threads
b.set(true);
// ... some time later
b.set(true);在这个“虚拟”set(true)之后,当读取线程调用get()时会有性能损失吗?读取线程必须重新读取并缓存值吗?
如果是这样的话,写线程可能会这样做:
b.compareAndSet(false, true);这样,读取线程只需对实际更改无效。
发布于 2018-11-22 17:14:57
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是已生成,如
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文档所言:
此指令可与锁前缀一起使用,以允许以原子方式执行指令。为了简化到处理器总线的接口,目标操作数接收一个写周期,而不考虑比较的结果。如果比较失败,则返回目标操作数;否则,源操作数将写入目标。(处理器不会产生锁定的读,同时也会产生锁定的写。)
因此,在多处理器x86机器上,NOP也会进行写操作,从而影响缓存行.(重点是我补充的)
发布于 2018-11-22 16:33:24
写和CAS都“触摸”了触发缓存行脏的高速缓存行。
然而,成本相对较低,在30-50 ns之间。
由于代码还没有运行10,000次,所以代码的成本可能要高得多。
https://stackoverflow.com/questions/53434995
复制相似问题