thread teoria temporizador programación partes metodo hilos gráficos control con java multithreading concurrency volatile compiler-bug

teoria - temporizador en java con hilos



¿Por qué no volátil en java 5+ asegura la visibilidad desde otro hilo? (4)

Basado en el extracto de JCiP a continuación, hubiera pensado que su ejemplo nunca debería imprimir "error":

Los efectos de visibilidad de las variables volátiles se extienden más allá del valor de la variable volátil en sí misma. Cuando un hilo A escribe en una variable volátil y posteriormente el hilo B lee esa misma variable, los valores de todas las variables que fueron visibles para A antes de escribir en la variable volátil se vuelven visibles para B después de leer la variable volátil.

De acuerdo a:

http://www.ibm.com/developerworks/library/j-jtp03304/

Bajo el nuevo modelo de memoria, cuando el hilo A escribe en una variable volátil V, y el hilo B lee desde V, todos los valores de variables que fueron visibles para A en el momento en que se escribió V ahora están garantizados para ser visibles para B

Y muchos lugares en Internet afirman que el siguiente código nunca debería imprimir "error":

public class Test { volatile static private int a; static private int b; public static void main(String [] args) throws Exception { for (int i = 0; i < 100; i++) { new Thread() { @Override public void run() { int tt = b; // makes the jvm cache the value of b while (a==0) { } if (b == 0) { System.out.println("error"); } } }.start(); } b = 1; a = 1; } }

b debe ser 1 para todos los hilos cuando a es 1.

Sin embargo , a veces obtengo un "error" impreso . ¿Cómo es esto posible?


En mi opinión, el problema se debe a la falta de sincronización :

AVISO: si b = 1 heppens antes de a = 1, y a es volátil mientras b no lo es, entonces b = 1 realmente se actualiza para todos los hilos solo después de que a = 1 haya terminado (de acuerdo con la lógica del quate).

lo que heppend en su código es que b = 1 se actualizó primero para el proceso principal solamente, solo cuando la asignación volátil finalizó, todos los hilos b se actualizaron. Creo que tal vez las asignaciones de volátiles no funcionan como operaciones atómicas (necesita apuntar lejos y de alguna manera actualizar el resto de las referencias para actuar como volátiles) así que esta sería mi suposición de por qué un hilo lee b = 0 en lugar de b = 1.

Considere este cambio en el código, que muestra mi reclamo:

public class Test { volatile static private int a; static private int b; private static Object lock = new Object(); public static void main(String [] args) throws Exception { for (int i = 0; i < 100; i++) { new Thread() { @Override public void run() { int tt = b; // makes the jvm cache the value of b while (true) { synchronized (lock ) { if (a!=0) break; } } if (b == 0) { System.out.println("error"); } } }.start(); } b = 1; synchronized (lock ) { a = 1; } } }



Actualizar:

Para cualquier persona interesada, este error se ha solucionado y solucionado para Java 7u6 build b14. Puede ver el informe de errores / arreglos aquí

Respuesta original

Cuando piense en términos de la visibilidad / orden de la memoria, tendrá que pensar en su relación de pase-antes. La pre condición importante para b != 0 es para a == 1 . Si a != 1 entonces b puede ser 0 o 1.

Una vez que un hilo ve a == 1 entonces ese hilo está garantizado para ver b == 1 .

Publicar Java 5, en el ejemplo de OP, una vez que el while(a == 0) rompe, se garantiza que será 1

Editar:

Ejecuté la simulación muchas veces y no vi tu salida.

¿Con qué sistema operativo, versión Java y CPU está probando?

Estoy en Windows 7, Java 1.6_24 (intentando con _31)

Editar 2:

Felicitaciones al OP y a Walter Laan: para mí solo sucedió cuando cambié de Java de 64 bits a Java de 32 bits, en (pero no puede excluirse) a Windows 7 de 64 bits.

Editar 3:

La asignación a tt , o más bien al staticget de b parece tener un impacto significativo (para probar que esto elimina el int tt = b; y siempre debería funcionar).

Parece que la carga de b en tt almacenará el campo localmente, que luego se usará en el coniditonal (la referencia a ese valor no tt ). Entonces, si b == 0 es verdadero, probablemente signifique que la tienda local en tt fue 0 (en este punto es una carrera para asignar 1 a tt local). Esto parece ser cierto solo para 32 Bit Java 1.6 y 7 con el conjunto de clientes.

Comparé los dos conjuntos de salida y la diferencia inmediata fue aquí. (Tenga en cuenta que estos son fragmentos).

Este impreso "error"

0x021dd753: test %eax,0x180100 ; {poll} 0x021dd759: cmp $0x0,%ecx 0x021dd75c: je 0x021dd748 ;*ifeq ; - Test$1::run@7 (line 13) 0x021dd75e: cmp $0x0,%edx 0x021dd761: jne 0x021dd788 ;*ifne ; - Test$1::run@13 (line 17) 0x021dd767: nop 0x021dd768: jmp 0x021dd7b8 ; {no_reloc} 0x021dd76d: xchg %ax,%ax 0x021dd770: jmp 0x021dd7d2 ; implicit exception: dispatches to 0x021dd7c2 0x021dd775: nop ;*getstatic out ; - Test$1::run@16 (line 18) 0x021dd776: cmp (%ecx),%eax ; implicit exception: dispatches to 0x021dd7dc 0x021dd778: mov $0x39239500,%edx ;*invokevirtual println

Y

Esto no imprimió "error"

0x0226d763: test %eax,0x180100 ; {poll} 0x0226d769: cmp $0x0,%edx 0x0226d76c: je 0x0226d758 ;*ifeq ; - Test$1::run@7 (line 13) 0x0226d76e: mov $0x341b77f8,%edx ; {oop(''Test'')} 0x0226d773: mov 0x154(%edx),%edx ;*getstatic b ; - Test::access$0@0 (line 3) ; - Test$1::run@10 (line 17) 0x0226d779: cmp $0x0,%edx 0x0226d77c: jne 0x0226d7a8 ;*ifne ; - Test$1::run@13 (line 17) 0x0226d782: nopw 0x0(%eax,%eax,1) 0x0226d788: jmp 0x0226d7ed ; {no_reloc} 0x0226d78d: xchg %ax,%ax 0x0226d790: jmp 0x0226d807 ; implicit exception: dispatches to 0x0226d7f7 0x0226d795: nop ;*getstatic out ; - Test$1::run@16 (line 18) 0x0226d796: cmp (%ecx),%eax ; implicit exception: dispatches to 0x0226d811 0x0226d798: mov $0x39239500,%edx ;*invokevirtual println

En este ejemplo, la primera entrada es de una ejecución que imprimió "error" mientras que la segunda fue de una que no lo hizo.

Parece que la ejecución de trabajo se cargó y se le asignó correctamente antes de probarla igual a 0.

0x0226d76e: mov $0x341b77f8,%edx ; {oop(''Test'')} 0x0226d773: mov 0x154(%edx),%edx ;*getstatic b ; - Test::access$0@0 (line 3) ; - Test$1::run@10 (line 17) 0x0226d779: cmp $0x0,%edx 0x0226d77c: jne 0x0226d7a8 ;*ifne ; - Test$1::run@13 (line 17)

Mientras que la ejecución que imprimió "error" cargó la versión en caché de %edx

0x021dd75e: cmp $0x0,%edx 0x021dd761: jne 0x021dd788 ;*ifne ; - Test$1::run@13 (line 17)

Para aquellos que tienen más experiencia con el ensamblador, por favor pesen :)

Editar 4

Debería ser mi última edición, ya que el desarrollador de simultaneidad tiene algo que ver, lo hice con y sin int tt = b; asignación un poco más. Descubrí que cuando aumento el máximo de 100 a 1000, parece haber una tasa de error del 100% cuando int tt = b está incluido y un 0% de probabilidad cuando se excluye.