studio programacion para móviles libro edición desarrollo desarrollar curso aprende aplicaciones java concurrency jvm java-memory-model

java - para - manual de programacion android pdf



Efectos de memoria de sincronización en Java (7)

Las actualizaciones de x necesitan la sincronización, pero, ¿la adquisición del bloqueo borra el valor de y también de la memoria caché? No puedo imaginar que ese sea el caso, porque si fuera cierto, las técnicas como el bloqueo de bandas podrían no ser de ayuda.

No estoy seguro, pero creo que la respuesta puede ser "sí". Considera esto:

class Foo { int x = 1; int y = 1; .. void bar() { synchronized (aLock) { x = x + 1; } y = y + 1; } }

Ahora este código no es seguro, dependiendo de lo que ocurra en el resto del programa. Sin embargo, creo que el modelo de memoria significa que el valor de y visto por la bar no debe ser más antiguo que el valor "real" en el momento de la adquisición del bloqueo. Eso implicaría que la memoria caché debe ser invalidada para y así como para x .

¿Puede la JVM analizar el código de manera confiable para asegurarse de que y no se modifique en otro bloque sincronizado utilizando el mismo bloqueo?

Si el bloqueo es this , este análisis parece factible como una optimización global una vez que todas las clases hayan sido precargadas. (No estoy diciendo que sería fácil o que valga la pena ...)

En casos más generales, el problema de probar que un bloqueo dado solo se usa alguna vez en relación con una instancia de "posesión" determinada probablemente sea insoluble.

Preguntas frecuentes JSR-133 dice:

Pero hay más en la sincronización que la exclusión mutua. La sincronización asegura que las escrituras de la memoria por un hilo antes o durante un bloque sincronizado se hacen visibles de manera predecible a otros hilos que se sincronizan en el mismo monitor. Después de salir de un bloque sincronizado, lanzamos el monitor, que tiene el efecto de enjuagar el caché a la memoria principal, de modo que las escrituras hechas por este hilo puedan ser visibles para otros hilos. Antes de que podamos ingresar un bloque sincronizado, adquirimos el monitor, que tiene el efecto de invalidar el caché del procesador local para que las variables se vuelvan a cargar desde la memoria principal. Entonces podremos ver todas las escrituras hechas visibles por la versión anterior.

También recuerdo haber leído que en las VM modernas de Sun las sincronizaciones no previstas son baratas. Estoy un poco confundido por este reclamo. Considere un código como:

class Foo { int x = 1; int y = 1; .. synchronized (aLock) { x = x + 1; } }

Las actualizaciones de x necesitan la sincronización, pero, ¿la adquisición del bloqueo borra el valor de y también de la memoria caché? No puedo imaginar que ese sea el caso, porque si fuera cierto, las técnicas como el bloqueo de bandas podrían no ser de ayuda. Alternativamente, ¿puede la JVM analizar el código de manera confiable para asegurarse de que y no se modifique en otro bloque sincronizado utilizando el mismo bloqueo y, por lo tanto, no volcar el valor de y en la caché al ingresar al bloque sincronizado?


BeeOnRope tiene razón, el texto que cita profundiza más en los detalles de implementación típicos que en lo que el Modelo de Memoria de Java realmente garantiza. En la práctica, a menudo puede ver que y se purga realmente de cachés de CPU cuando se sincroniza en x (también, si x en su ejemplo era una variable volátil, en cuyo caso no es necesaria la sincronización explícita para desencadenar el efecto). Esto se debe a que en la mayoría de las CPU (tenga en cuenta que este es un efecto de hardware, no algo que JMM describe), la caché funciona en unidades denominadas líneas de caché, que generalmente son más largas que una palabra de máquina (por ejemplo, 64 bytes de ancho). Como solo las líneas completas pueden cargarse o invalidarse en la memoria caché, existen buenas posibilidades de que xey estén en la misma línea y que el enjuagar una de ellas también purgue la otra.

Es posible escribir un punto de referencia que muestre este efecto. Cree una clase con solo dos campos int volátiles y permita que dos subprocesos realicen algunas operaciones (por ejemplo, incrementos en un bucle largo), uno en uno de los campos y uno en el otro. Tiempo de la operación Luego, inserte 16 campos int entre los dos campos originales y repita la prueba (16 * 4 = 64). Tenga en cuenta que una matriz es solo una referencia, por lo que una matriz de 16 elementos no servirá. Puede ver una mejora significativa en el rendimiento porque las operaciones en un campo ya no influirán en el otro. Si esto funciona para usted dependerá de la implementación de JVM y la arquitectura del procesador. Lo he visto en la práctica en Sun JVM y en una laptop x64 típica, la diferencia en el rendimiento fue varias veces.


Como y está fuera del alcance del método sincronizado, no hay garantías de que los cambios en él sean visibles en otros hilos. Si desea garantizar que los cambios en y se vean iguales en todos los hilos, entonces todos los hilos deben usar sincronización al leer y escribir y.

Si algunos hilos cambian y de manera sincronizada y otros no, entonces obtendrás un comportamiento inesperado. Todo el estado mutable compartido entre subprocesos debe estar sincronizado para que haya garantías de ver los cambios entre subprocesos. Todo el acceso en todos los hilos al estado mutable compartido (variables) debe estar sincronizado.

Y sí, la JVM garantiza que, mientras se mantiene el bloqueo, ningún otro subproceso puede ingresar a una región de código protegida por el mismo bloqueo.


La respuesta corta es que JSR-133 va demasiado lejos en su explicación . Este no es un problema grave porque JSR-133 es un documento no normativo que no forma parte del lenguaje ni de los estándares de JVM. Más bien, es solo un documento que explica una estrategia posible que es suficiente para implementar el modelo de memoria, pero no es en general necesaria . Además de eso, el comentario sobre "cache flushing" está básicamente fuera de lugar ya que las arquitecturas esencialmente cero implementarían el modelo de memoria de Java haciendo cualquier tipo de "cache flushing" (y muchas arquitecturas ni siquiera tienen tales instrucciones).

El modelo de memoria Java se define formalmente en términos de visibilidad, atomicidad, relaciones antes-pasadas, etc., lo que explica exactamente qué hilos deben ver qué, qué acciones deben ocurrir antes de otras acciones y otras relaciones utilizando una definición precisa (matemáticamente) modelo. El comportamiento que no está formalmente definido podría ser aleatorio o estar bien definido en la práctica en alguna implementación de hardware y JVM, pero por supuesto nunca deberías confiar en esto, ya que podría cambiar en el futuro, y nunca podrías estar seguro de que para empezar, estaba bien definido, a menos que escribiera la JVM y estuviera al tanto de la semántica del hardware.

Por lo tanto, el texto que citó no describe formalmente lo que Java garantiza, sino que describe cómo una arquitectura hipotética que tenía garantías de orden y visibilidad de memoria muy débiles podría satisfacer los requisitos del modelo de memoria de Java mediante el vaciado de memoria caché. Cualquier discusión real de la limpieza del caché, la memoria principal, etc., claramente no es aplicable en general a Java, ya que estos conceptos no existen en el lenguaje abstracto y la especificación del modelo de memoria.

En la práctica, las garantías ofrecidas por el modelo de memoria son mucho más débiles que una descarga completa: tener todas las operaciones atómicas, relacionadas con la concurrencia o de bloqueo para enjuagar todo el caché sería prohibitivamente caro, y esto casi nunca se hace en la práctica. Por el contrario, se utilizan operaciones especiales de CPU atómica, a veces en combinación con instrucciones de barrera de memoria , que ayudan a garantizar la visibilidad y el orden de la memoria. Por lo tanto, la aparente inconsistencia entre la sincronización barata no controlada y "enjuagar completamente la memoria caché" se resuelve al señalar que la primera es verdadera y la segunda no; el modelo de memoria Java no requiere descarga completa (y no se produce descarga en la práctica).

Si el modelo de memoria formal es demasiado pesado para digerir (no estarías solo), también puedes profundizar más en este tema echando un vistazo al libro de cocina de Doug Lea , que de hecho está relacionado en las preguntas frecuentes de JSR-133. pero viene al tema desde una perspectiva de hardware concreto, ya que está destinado a los escritores de compiladores. Allí, hablan exactamente de las barreras que se necesitan para determinadas operaciones, incluida la sincronización, y las barreras que allí se discuten se pueden asignar fácilmente al hardware real. Gran parte del mapeo real se discute en el libro de cocina.


es posible que desee comprobar la documentación de jdk6.0 http://java.sun.com/javase/6/docs/api/java/util/concurrent/package-summary.html#MemoryVisibility

Propiedades de consistencia de memoria El Capítulo 17 de la Especificación de Lenguaje Java define la relación de pasar antes a las operaciones de memoria, tales como lecturas y escrituras de variables compartidas. Se garantiza que los resultados de un subproceso de escritura por uno sean visibles para una lectura de otro subproceso solo si la operación de escritura ocurre, antes de la operación de lectura. Las construcciones sincronizadas y volátiles, así como los métodos Thread.start () y Thread.join (), pueden formar relaciones de pase previo. En particular:

  • 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 un desbloqueo (bloqueo sincronizado o salida de método) de un monitor, antes de cada bloqueo posterior (bloqueo sincronizado o entrada de método) de ese mismo monitor. Y dado que la relación de pasar antes es transitiva, todas las acciones de un hilo antes de desbloquear suceden, antes de todas las acciones posteriores a cualquier bloqueo de hilo que monitorice.
  • 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.
  • Se produce una llamada para iniciar un hilo, antes de cualquier acción en el hilo iniciado.
  • Todas las acciones en un hilo ocurren, antes de que cualquier otro hilo vuelva exitosamente de una unión en ese hilo

Entonces, como se afirma en el punto resaltado arriba: todos los cambios que ocurren antes de que ocurra un desbloqueo en un monitor son visibles para todos los hilos (y en su propio bloque de sincronización) que se conectan al mismo monitor. Esto está de acuerdo con el paso de Java -Antes de la semántica. Por lo tanto, todos los cambios realizados en y también se eliminarán en la memoria principal cuando otro subproceso adquiera el monitor en ''aLock''.


sincronizar garantías, que solo un hilo puede ingresar a un bloque de código. Pero no garantiza que las modificaciones de variables hechas dentro de la sección sincronizada serán visibles para otros hilos. Solo los hilos que entran en el bloque sincronizado están garantizados para ver los cambios. Los efectos de memoria de la sincronización en Java podrían compararse con el problema del bloqueo con doble control con respecto a c ++ y el bloqueo con doble control de Java es ampliamente citado y se utiliza como un método eficiente para implementar la inicialización lenta en un entorno de subprocesos múltiples. Desafortunadamente, no funcionará confiablemente de una manera independiente de la plataforma cuando se implemente en Java , sin sincronización adicional. Cuando se implementa en otros lenguajes, como C ++, depende del modelo de memoria del procesador, las reordenaciones realizadas por el compilador y la interacción entre el compilador y la biblioteca de sincronización. Dado que ninguno de estos se especifica en un lenguaje como C ++, poco se puede decir acerca de las situaciones en las que funcionará. Las barreras de memoria explícitas se pueden usar para hacer que funcione en C ++, pero estas barreras no están disponibles en Java.


somos desarrolladores de Java, ¡solo conocemos máquinas virtuales, no máquinas reales!

déjame teorizar sobre lo que está sucediendo, pero debo decir que no sé de lo que estoy hablando.

decir que el hilo A se está ejecutando en la CPU A con el caché A, el hilo B se está ejecutando en la CPU B con el caché B,

  1. el hilo A dice y; La CPU A extrae y de la memoria principal y guardó el valor en la memoria caché A.

  2. el subproceso B asigna un nuevo valor a ''y''. VM no tiene que actualizar la memoria principal en este punto; en lo que respecta al subproceso B, puede leer / escribir en una imagen local de ''y''; tal vez la ''y'' no es más que un registro de CPU.

  3. el subproceso B sale de un bloque de sincronización y libera un monitor. (cuándo y dónde ingresó el bloque no importa). El hilo B ha actualizado bastantes variables hasta este punto, incluyendo ''y''. Todas esas actualizaciones deben escribirse en la memoria principal ahora.

  4. La CPU B escribe el nuevo valor y para colocar ''y'' en la memoria principal. (Imagino que) casi INSTANTÁNEAMENTE, la información ''main y is updated'' está conectada a la memoria caché A, y la memoria caché A invalida su propia copia de y. Eso debe haber sucedido realmente RÁPIDO en el hardware.

  5. el hilo A adquiere un monitor y entra en un bloque de sincronización; en este punto, no tiene que hacer nada con respecto a la memoria caché A. ''y'' ya ha salido de la memoria caché A. cuando el hilo A vuelve a leer y, está recién salido de la memoria principal con nuevo valor asignado por B.

considere otra variable z, que también fue almacenada en caché por A en el paso (1), pero no es actualizada por el hilo B en el paso (2). puede sobrevivir en el caché A hasta el final del paso (5). el acceso a ''z'' no se ralentiza debido a la sincronización.

si las declaraciones anteriores tienen sentido, entonces, de hecho, el costo no es muy alto.

Además del paso (5): el hilo A puede tener su propio caché que es incluso más rápido que el caché A - puede usar un registro para la variable ''y'', por ejemplo. eso no será invalidado por el paso (4), por lo tanto, en el paso (5), el hilo A debe borrar su propio caché al ingresar la sincronización. eso no es una gran pena sin embargo.