Uso una AtomicBoolean
para hacer cumplir volatile
la visibilidad entre los hilos. Un subproceso está actualizando el valor, otro hilo solamente lo está leyendo.
Digamos que el valor actual es true
. Ahora dicen que un hilo de escritura establece su valor a true
nuevo:
final AtomicBoolean b = new AtomicBoolean(); // shared between threads
b.set(true);
// ... some time later
b.set(true);
Después de esto 'ficticio' set(true)
, ¿hay una penalización en el rendimiento cuando el hilo de lectura llamadas get()
? ¿El hilo de lectura tiene que volver a leer y almacenar en caché el valor?
Si ese es el caso, el hilo de escritura podría haber hecho:
b.compareAndSet(false, true);
De esta manera, el hilo de lectura solamente tiene que invalidar los cambios reales.
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()
Ya es nativo:
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
Cuando Atomic::cmpxchg
se genera algún lugar al comienzo de la ejecución de JVM como
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()
genera código x86 (que tiene una larga, camino de código heredado también, así que no copio que uno aquí):
InstructionMark im(this);
prefix(adr, reg);
emit_byte(0x0F);
emit_byte(0xB1);
emit_operand(reg, adr);
0F
B1
es en realidad una CMPXCHG
operación. Si marca el código anterior, if ( os::is_MP() ) __ lock();
emite un LOCK
prefijo en máquinas multiprocesador (permítanme citar a saltar lock()
, se emite un solo F0
byte), por lo que prácticamente en todas partes.
Y como los CMPXCHG
documentos dice:
Esta instrucción se puede utilizar con un prefijo LOCK para permitir que la instrucción a ser ejecutada atómicamente. Para simplificar la interfaz de bus del procesador, el operando de destino recibe un ciclo de escritura sin tener en cuenta el resultado de la comparación. El operando de destino se escribe de nuevo si la comparación falla; de lo contrario, el operando fuente está escrito en el destino. ( El procesador nunca se produce una bloqueado leer sin producir también una escritura bloqueado. )
Así que en una máquina con varios procesadores x86, el NOP-CAS también hace una escritura, que afectan a la línea de caché. (El énfasis fue añadido por mí)