java concurrency memory-barriers java-memory-model

La visibilidad simultánea de Java de la matriz primitiva escribe



concurrency memory-barriers (4)

Se menciona correctamente la regla n. ° 2 de la relación de suceso anterior.

Se produce una escritura en un campo volátil, antes de cada lectura posterior de ese mismo campo.

Sin embargo, no garantiza que se llame nunca a publish () antes de SyncChanges () en la línea de tiempo absoluta. Vamos a cambiar tu ejemplo un poco.

Tema 1:

matrix[0][0] = 42.0f; Thread.sleep(1000*1000); // assume the thread was preempted here publisher.publish(); //assume initial state of sync is 0

Tema 2:

int a = publisher.syncChanges(); float b = matrix[0][0];

¿Cuáles son las opciones para que las variables a y b estén disponibles?

  • a es 0, b puede ser 0 o 42
  • a es 1, b es 42 debido a la relación de pasar antes de
  • a es mayor que 1 (el Subproceso 2 fue lento por alguna razón y el Subproceso 1 tuvo la suerte de publicar las actualizaciones varias veces), el valor de b depende de la lógica comercial y de la forma en que se maneja la matriz. ¿Depende del estado anterior o no?

¿Como lidiar con? Depende de la lógica de negocios.

  • Si el hilo 2 sondea el estado de una matriz de vez en cuando y está perfectamente bien tener algunos valores desactualizados entre ellos, si al final se procesará el valor correcto, déjalo tal como está.
  • Si al Tema 2 no le importan las actualizaciones perdidas, pero siempre quiere observar una matriz actualizada, entonces use las colecciones de copiar sobre escritura o use ReaderWriteLock como se mencionó anteriormente.
  • Si a Thread 2 le importan las actualizaciones únicas, entonces debería manejarse de una manera más inteligente, puede considerar el patrón wait () / notify () y notificar a Thread 2 cada vez que se actualice la matriz.

Recientemente encontré esta joya en mi base de código:

/** This class is used to "publish" changes to a non-volatile variable. * * Access to non-volatile and volatile variables cannot be reordered, * so if you make changes to a non-volatile variable before calling publish, * they are guaranteed to be visible to a thread which calls syncChanges * */ private static class Publisher { //This variable may not look like it''s doing anything, but it really is. //See the documentaion for this class. private volatile AtomicInteger sync = new AtomicInteger(0); void publish() { sync.incrementAndGet(); } /** * * @return the return value of this function has no meaning. * You should not make *any* assumptions about it. */ int syncChanges() { return sync.get(); } }

Esto se usa como tal:

Tema 1

float[][] matrix; matrix[x][y] = n; publisher.publish();

Tema 2

publisher.syncChanges(); myVar = matrix[x][y];

El subproceso 1 es un subproceso de actualización de fondo que se ejecuta continuamente. El subproceso 2 es un subproceso de trabajador HTTP al que no le importa que lo que lee sea de alguna manera consistente o atómico, solo que las escrituras "eventualmente" llegan y no se pierden como ofrendas a los dioses de simultaneidad.

Ahora, esto activa todas mis campanas de advertencia. Algoritmo de concurrencia personalizado escrito en el interior del código no relacionado.

Desafortunadamente, arreglar el código no es trivial. El soporte de Java para matrices primitivas concurrentes no es bueno. Parece que la forma más clara de arreglar esto es usar un ReadWriteLock , pero eso probablemente tendría implicaciones negativas en el rendimiento. La corrección es más importante, claramente, pero parece que debería demostrar que esto no es correcto antes de arrancarlo de un área sensible al rendimiento.

De acuerdo con la documentación de java.util.concurrent , las siguientes relaciones de creación de happens-before :

Cada acción en un hilo ocurre, antes de cada acción en ese hilo que viene más tarde en el orden del programa.

Se produce una escritura en un campo volátil, antes de cada lectura posterior de ese mismo campo. Las escrituras y lecturas de campos volátiles tienen efectos de coherencia de memoria similares a los de entrada y salida de monitores, pero no implican el bloqueo de exclusión mutua.

Entonces suena como:

  • la escritura de matriz ocurre antes de publicar () (regla 1)
  • publish () sucede -antes de SyncChanges () (regla 2)
  • syncChanges () ocurre antes de la lectura de matriz (regla 1)

Entonces el código de hecho ha establecido una cadena de pasar antes de la matriz.

Pero no estoy convencido. La concurrencia es difícil, y no soy un experto en dominios. ¿Qué me he perdido? ¿Esto es seguro?


En términos de visibilidad, todo lo que necesita es escribir y leer de forma volátil en cualquier campo volátil. Esto funcionaría

final float[][] matrix = ...; volatile float[][] matrixV = matrix;

Tema 1

matrix[x][y] = n; matrixV = matrix; // volatile write

Tema 2

float[][] m = matrixV; // volatile read myVar = m[x][y]; or simply myVar = matrixV[x][y];

Pero esto solo es bueno para actualizar una variable. Si los hilos del escritor escriben múltiples variables y el hilo de lectura los está leyendo, el lector puede ver una imagen incoherente. Por lo general, se trata con un bloqueo de lectura y escritura. Copy-on-write podría ser adecuado para algunos patrones de uso.

Doug Lea tiene un nuevo "StampedLock" http://gee.cs.oswego.edu/dl/jsr166/dist/jsr166edocs/jsr166e/StampedLock.html para Java8, que es una versión de bloqueo de lectura y escritura mucho más económica para leer cabellos. Pero es mucho más difícil de usar también. Básicamente, el lector obtiene la versión actual, luego sigue leyendo un conjunto de variables, luego verifica la versión nuevamente; si la versión no ha cambiado, no hubo escrituras concurrentes durante la sesión de lectura.


Esto parece seguro para publicar actualizaciones únicas en la matriz, pero, por supuesto, no proporciona ninguna atomicidad. Si eso está bien depende de su aplicación, pero probablemente debería documentarse en una clase de utilidad como esta.

Sin embargo, contiene algo de redundancia y podría mejorarse haciendo que el campo de sync final . El acceso volatile de este campo es el primero de dos barreras de memoria; por contrato, llamar a incrementAndGet() tiene el mismo efecto en la memoria que una escritura y una lectura en una variable volátil, y llamar a get() tiene el mismo efecto que una lectura.

Por lo tanto, el código puede basarse en la sincronización provista por estos métodos solo, y hacer que el campo sea final .


Usar volatile no es una bala mágica para sincronizar todo. Se garantiza que si otro hilo lee el valor actualizado de una variable volátil, también verá todos los cambios realizados en una variable no volátil antes de eso. Pero nada garantiza que el otro hilo leerá el valor actualizado .

En el código de ejemplo, si realiza varias escrituras en la matrix y luego llama a publish() , y el otro hilo llama a synch() y luego lee la matriz, entonces el otro subproceso puede ver algunos, todos o ninguno de los cambios:

  • Todos los cambios, si se lee el valor actualizado de publish ()
  • Ninguno de los cambios, si se lee el antiguo valor publicado y ninguno de los cambios se ha filtrado a través
  • Algunos de los cambios, si se lee el valor publicado anteriormente, pero algunos de los cambios se han filtrado a través de

Ver este artículo