threads - lock object java
Consistencia de la memoria: relaciĆ³n "sucede antes de" en Java (4)
Considera esto:
static int x = 0;
public static void main(String[] args) {
x = 1;
Thread t = new Thread() {
public void run() {
int y = x;
};
};
t.start();
}
El hilo principal ha cambiado el campo x
. El modelo de memoria Java no garantiza que este cambio sea visible para otros hilos si no están sincronizados con el hilo principal. Pero el hilo t
verá este cambio porque el hilo principal llamado t.start()
y JLS garantizan que llamar a t.start()
hace que el cambio a x
visible en t.run()
por lo que y
se garantiza que se le asignará 1
.
Lo mismo Thread.join();
con Thread.join();
Esta pregunta ya tiene una respuesta aquí:
- Cómo entender sucede antes de respuestas consistentes 4
Mientras lee documentos Java sobre errores de consistencia de memoria. Encuentro puntos relacionados con dos acciones que crean que suceden: antes de la relación:
Cuando una instrucción invoca
Thread.start()
, cada instrucción que tiene una relación de pasar antes con esa declaración también tiene una relación de pasar antes con cada instrucción ejecutada por el nuevo hilo. Los efectos del código que condujo a la creación del nuevo hilo son visibles para el nuevo hilo.Cuando un hilo termina y provoca un
Thread.join()
en otro hilo para devolver, entonces todas las instrucciones ejecutadas por el terminado
hilo tiene una relación de pasar antes con todas las declaraciones
siguiendo la unión exitosa. Los efectos del código en el hilo ahora son visibles para el hilo que realizó la unión.
No soy capaz de entender su significado. Sería genial si alguien lo explicara con un simple ejemplo.
De acuerdo con el documento Oracle, definen que la relación de pasar antes es simplemente una garantía de que la memoria se escribe por una declaración específica son visibles para otra declaración específica.
package happen.before;
public class HappenBeforeRelationship {
private static int counter = 0;
private static void threadPrintMessage(String msg){
System.out.printf("[Thread %s] %s/n", Thread.currentThread().getName(), msg);
}
public static void main(String[] args) {
threadPrintMessage("Increase counter: " + ++counter);
Thread t = new Thread(new CounterRunnable());
t.start();
try {
t.join();
} catch (InterruptedException e) {
threadPrintMessage("Counter is interrupted");
}
threadPrintMessage("Finish count: " + counter);
}
private static class CounterRunnable implements Runnable {
@Override
public void run() {
threadPrintMessage("start count: " + counter);
counter++;
threadPrintMessage("stop count: " + counter);
}
}
}
La salida será:
[Thread main] Increase counter: 1
[Thread Thread-0] start count: 1
[Thread Thread-0] stop count: 2
[Thread main] Finish count: 2
Eche un vistazo a la salida, línea [Thread Thread-0] start count: 1 muestra que todos los cambios de contador antes de la invocación de Thread.start () son visibles en el cuerpo de Thread.
Y la línea [Thread main] Finish count: 2 indica que todos los cambios en el cuerpo del Thread son visibles para el hilo principal que llama a Thread.join ().
Espero que pueda ayudarte con claridad.
Las CPU modernas no siempre escriben datos en la memoria en el orden en que se actualizaron, por ejemplo, si ejecuta el pseudo código (asumiendo que las variables siempre se almacenan en la memoria aquí para simplificar);
a = 1
b = a + 1
... la CPU puede escribir b
en la memoria antes de escribir en la memoria. Esto no es realmente un problema siempre que ejecute cosas en un solo subproceso, ya que el subproceso que ejecuta el código anterior nunca verá el valor anterior de ninguna de las variables una vez que se hayan realizado las asignaciones.
El subprocesamiento múltiple es otra cuestión, se podría pensar que el siguiente código permitiría que otro subproceso recoja el valor de su cálculo pesado;
a = heavy_computation()
b = DONE
... el otro hilo haciendo ...
repeat while b != DONE
nothing
result = a
El problema es que la bandera hecha se puede configurar en la memoria antes de que el resultado se almacene en la memoria, por lo que la otra cadena puede recoger el valor de la dirección de memoria a antes de escribir el resultado de la computación en la memoria.
El mismo problema sería: si Thread.start
y Thread.join
no tuvieran una garantía de "pasar antes" , le daría problemas con el código;
a = 1
Thread.start newthread
...
newthread:
do_computation(a)
... ya que a
puede que no tenga un valor almacenado en la memoria cuando se inicia el hilo.
Como casi siempre quiere que el nuevo hilo pueda usar los datos que inicializó antes de iniciarlo, Thread.start
tiene una garantía de "pasar antes", es decir, los datos que se han actualizado antes de llamar a Thread.start
están garantizados para estar disponibles para el nuevo hilo . Lo mismo ocurre con Thread.join
donde se Thread.join
que los datos escritos por el nuevo hilo sean visibles para el hilo que se une después de la terminación .
Simplemente hace que el enhebrado sea mucho más fácil.
Los problemas de visibilidad del subproceso pueden ocurrir en un código que no está correctamente sincronizado de acuerdo con el modelo de memoria de Java. Debido a optimizaciones de compilador y hardware, las escrituras por un hilo no siempre son visibles por lecturas de otro hilo. El modelo de memoria de Java es un modelo formal que hace que las reglas de "sincronización correcta" sean claras, de modo que los programadores puedan evitar problemas de visibilidad de subprocesos.
"Happens-before" es una relación definida en ese modelo, y se refiere a ejecuciones específicas. Una escritura W que se ha comprobado que ocurre, antes de que una lectura R quede garantizada por esa lectura, suponiendo que no hay otra escritura interferente (es decir, una que no ocurre) antes de la relación con la lectura, o una ocurriendo entre ellas de acuerdo con esa relación).
El tipo más simple de relación sucede antes de que ocurra entre acciones en el mismo hilo. Una escritura de W a V en el hilo P ocurre -antes de una lectura R de V en el mismo hilo, suponiendo que W viene antes de R de acuerdo con el orden del programa.
El texto al que se refiere establece que thread.start () y thread.join () también garantizan una relación de "sucede antes de". Cualquier acción que ocurra antes de thread.start () también ocurre antes de cualquier acción dentro de ese hilo. De forma similar, las acciones dentro del hilo ocurren antes de cualquier acción que aparezca después de thread.join ().
¿Cuál es el significado práctico de eso? Si, por ejemplo, inicia un hilo y espera a que termine de una manera no segura (por ejemplo, un sueño durante mucho tiempo, o prueba alguna bandera no sincronizada), entonces, cuando intente leer las modificaciones de datos realizadas por el hilo, es posible que los vea parcialmente, lo que conlleva el riesgo de inconsistencias en los datos. El método join () actúa como una barrera que garantiza que cualquier fragmento de datos publicado por el hilo sea visible por completo y de manera consistente por el otro hilo.