2010-04-05 17 views
8

El modelo de java meomry indica que los bloques synchronize que se sincronizan en el mismo monitor imponen una antes-después-real en las variables modificadas dentro de esos bloques. Ejemplo:Modelo de memoria Java: reordenamiento y bloqueos concurrentes

// in thread A 
synchronized(lock) 
{ 
    x = true; 
} 

// in thread B 
synchronized(lock) 
{ 
    System.out.println(x); 
} 

En este caso está garantizada que el hilo B verá x==true tanto tiempo como hilo A ya pasó que synchronized -bloque. Ahora estoy en el proceso de reescribir muchos códigos para usar los bloqueos más flexibles (y se dice que son más rápidos) en java.util.concurrent, especialmente en ReentrantReadWriteLock. Así que el ejemplo es el siguiente:

EDITAR: El ejemplo se rompió, porque incorrectamente transformado el código, como se ha señalado por Matt B. Fijan como sigue:

// in thread A 
lock.writeLock().lock(); 
{ 
    x = true; 
} 
lock.writeLock().unlock(); 

// in thread B 
lock.readLock().lock(); 
{ 
    System.out.println(x); 
} 
lock.readLock().unlock(); 

Sin embargo, no he visto ninguna pista dentro de la especificación del modelo de memoria que tales cerraduras también implican el ordenamiento nessessary. Al observar la implementación, parece depender del acceso a las variables volátiles dentro de AbstractQueuedSynchronizer (para la implementación del sol al menos). Sin embargo, esto no forma parte de ninguna especificación y, además, el acceso a las variables no volátiles no está realmente cubierto por la barrera de memoria que ofrecen estas variables, ¿o sí?

lo tanto, aquí están mis preguntas:

  • ¿Es seguro asumir el mismo orden que con los "viejos" synchronized bloques?
  • ¿Está documentado esto en alguna parte?
  • ¿Está accediendo a cualquier variable volátil una barrera de memoria para cualquier otra variable?

Saludos, Steffen

-

comentario a Yanamon:

mira el siguiente código:

// in thread a 
x = 1; 
synchronized (a) { y = 2; } 
z = 3; 

// in thread b 
System.out.println(x); 
synchronized (a) { System.out.println(y); } 
System.out.println(z); 

Por lo que entendí, la barrera de memoria hace cumplir la segunda salida para mostrar 2, pero no tiene efecto garantizado en las otras variables ...? Entonces, ¿cómo se puede comparar esto con acceder a una variable volátil?

+0

Una nota sobre el código que ha agregado, thread b solo imprimirá 2 si obtiene el candado de un thread anterior a ... eso fue algo implícito, pero solo quería dejar eso en claro. Pero para responder a su pregunta volátil, volátil se debe utilizar de la siguiente manera para forzar la visibilidad: -------- volátil boolean memoryBarrier = false; int unguardedValue = 0; // thread a: unguardedValue = 10; memoryBarrier = true; // thread b if (memoryBarrier) { // unguardedValue se garantiza que se lee como 10; } – Yanamon

+0

Bueno, supongo que escribir código en los comentarios realmente no funciona bien, actualicé mi respuesta con un ejemplo – Yanamon

Respuesta

5

Desde el API-doc:

implementaciones

de todos los seguros deben cumplir la misma sincronización de la memoria semántica de lo dispuesto por la incorporada en bloqueo del monitor, como se describe en el Java Language Specification, tercera edición (Modelo de memoria 17.4):

* A successful lock operation has the same memory synchronization effects as a successful Lock action. 
* A successful unlock operation has the same memory synchronization effects as a successful Unlock action. 

de bloqueo y desbloqueo sin éxito operaciones, y reentrantes operaciones de bloqueo/desbloqueo, no requieren ningún efecto de sincronización de memoria .

+0

Tiene toda la razón. He leído muchas cosas, pero parece que me he perdido completamente la interfaz de Lock en sí ... –

+0

¿Puedes comentar sobre la volátil pregunta? –

+0

@Steffen Heil: si recuerdo correctamente, cualquier acceso a una variable volátil solo tiene efecto de sincronización con otros accesos a la misma variable, es decir, no se garantiza que proporcione algún tipo de barrera de memoria general. Una exploración rápida del JLS parece confirmar este recuerdo. Pero tome esto con un grano de sal, ya que ha pasado bastante tiempo desde la última vez que tuve un encuentro más cercano con el modelo de memoria y sus implicaciones ... – Dirk

4

Más allá de la cuestión de lo que garantiza la semántica del modelo de memoria, creo que hay algunos problemas con el código que está publicando.

  1. está sincronizando dos veces en la misma cerradura - esto es innecesario. Cuando se utiliza una implementación Lock, no es necesario utilizar el bloque synchronized.
  2. La expresión estándar para usar un Lock es hacerlo en un bloque try-finally para evitar el desbloqueo accidental del bloqueo (ya que el bloqueo no se libera automáticamente al ingresar cualquier bloque, como en el bloque synchronized).

Debe utilizar un Lock con algo parecido a:

lock.lock(); 
try { 
    //do stuff 
} 
finally { 
    lock.unlock(); 
} 
+0

Tienes razón, mi ejemplo estaba roto. Arreglé la pregunta. Y sí, normalmente estoy usando try/finally, solo lo dejo aquí por brevedad. –

1

de lectura y escritura de variables volátiles ahora hace cumplir que sucede antes y después de la operación pasa pedido. Escribir en una variable volátil tiene el mismo efecto que soltar un monitor y leer una variable tiene el efecto de adquirir un monitor. El siguiente ejemplo hace que sea un poco más claro:

volatile boolean memoryBarrier = false; 
int unguardedValue = 0; 

//thread a: 
unguardedValue = 10; 
memoryBarrier = true; 

// thread b 
if (memoryBarrier) { 
    // unguardedValue is guaranteed to be read as 10; 
} 

Pero todo eso se dice el código de ejemplo que nos ha facilitado no se veía como si hubiera sido realmente utilizando el ReentrantLock ya que fue diseñado para ser utilizado.

  1. que rodean el uso de un Lock con construido en syncronized palabra clave que hace que efectivamente el acceso al la de Java al bloqueo ya un solo subproceso por lo que no da la Lock la oportunidad de hacer ningún trabajo real.
  2. La adquisición de una liberación de un Lock debe hacerse siguiendo el patrón de abajo, esto se indica en la documentación de Java de Lock

lock.readLock().lock(); 
try { 
    // Do work 
} finally { 
    lock.readLock.unlock(); 
} 

+0

Ver el comentario en cuestión, por favor. –

+0

Entiendo que el hilo b no verá Valor no protegido porque el orden no afecta la visibilidad y el Valor no protegido no es volátil, por lo tanto, no necesariamente visible por el hilo b. Es esto correcto ? –

1

Yanamon, no estoy seguro de que son correctos - pero por diferentes razones que el argumento que estás haciendo.

La variable de variable no protegida se puede reordenar en el subproceso "a" de modo que su valor se establece en 10 después de memoryBarrier se establece en verdadero.

"no hay garantía de que las operaciones en un hilo se llevarán a cabo en el orden dado por el programa, siempre y cuando el reordenamiento no es detectable dentro de que hilo - incluso si el reordenamiento es evidente para otros hilos "

Java concurrencia en la práctica, Brian Goetz, P34

Actualizado: lo que he dicho es cierto en el caso del modelo de memoria de edad. Entonces, si quieres escribir una vez, correr en cualquier lugar, entonces mi argumento se mantiene. Sin embargo, en el nuevo modelo de memoria, no es el caso ya que la semántica que rodea el reordenamiento de las variables no volátiles en presencia de acceso volátil se ha vuelto más estricta (ver http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#volatile).

Cuestiones relacionadas