La variable volátil proporciona visibilidad de subprocesos y no garantiza la seguridad de los hilos y la atomicidad.
¿Qué es la visibilidad del hilo?
Los bloqueos proporcionan dos características principales: exclusión mutua y visibilidad. La exclusión mutua significa que solo un hilo puede contener un bloqueo específico a la vez, por lo que esta característica puede usarse para implementar un protocolo de acceso coordinado para datos compartidos, para que solo un hilo pueda usar los datos compartidos a la vez. La visibilidad es un poco más compleja, y debe garantizar que los cambios en los datos compartidos antes de liberar el bloqueo sean visibles a otro subproceso que posteriormente adquiere el bloqueo, sin esta garantía de visibilidad proporcionada por el mecanismo de sincronización, las variables compartidas vistas por el subproceso pueden ser valores precodificados o inconsistentes, lo que causará muchos problemas graves.
Ver la semántica del volátil :
Volátil es equivalente a una implementación débil de sincronizado, lo que significa que volátil implementa semántica sincronizada, pero no tiene un mecanismo de bloqueo. Asegura que las actualizaciones del campo volátil informen otros hilos de manera predecible.
Volátil contiene la siguiente semántica:
(1) El modelo de almacenamiento Java no reordenará las operaciones de instrucciones valátiles: esto garantiza que las operaciones en variables volátiles se ejecuten en el orden en que aparecen las instrucciones.
(2) La variable volátil no se almacenará en caché en el registro (solo los hilos son visibles) u otros lugares que no son visibles para la CPU. El resultado de la variable volátil siempre se lee desde la memoria principal cada vez. En otras palabras, para la modificación de la variable volátil, otros hilos siempre son visibles, y no están utilizando variables dentro de la pila de subprocesos. Es decir, en la Ley de Herenidos antes, después de escribir una variable valátil, cualquier operación de lectura posterior puede entenderse para ver el resultado de esta operación de escritura.
Aunque las características de la variable volátil son buenas, el volátil no puede garantizar la seguridad de los subprocesos. Es decir, la operación del campo volátil no es atómico. La variable volátil solo puede garantizar la visibilidad (otros hilos pueden comprender los resultados después de ver este cambio después de modificar un hilo). ¡Para garantizar la atomicidad, solo puede bloquearla hasta ahora!
Principios para usar volátil:
Tres principios para aplicar variables volátiles:
(1) Las variables de escritura no dependen del valor de esta variable, o solo un hilo modifica esta variable
(2) El estado de la variable no necesita participar en las limitaciones invariantes con otras variables
(3) Las variables de acceso no necesitan ser bloqueadas
De hecho, estas condiciones indican que estos valores válidos que se pueden escribir en la variable volátil son independientes del estado de cualquier programa, incluido el estado actual de la variable.
Los límites de la primera condición evitan que la variable volátil se use como un contador seguro de hilo. Aunque la operación incremental (x ++) parece una operación separada, en realidad es una operación combinada compuesta de una secuencia de operaciones de lectura-modificación-escritura que debe realizarse atómicamente, y el volátil no puede proporcionar las propiedades atómicas necesarias. La implementación de la operación correcta requiere mantener constante el valor de x durante la operación, lo que no es posible con la variable volátil. (Sin embargo, si el valor se ajusta para que se escriba solo desde un solo hilo, la primera condición se puede ignorar).
La mayoría de las situaciones de programación entran en conflicto con una de estas tres condiciones, lo que hace que la variable volátil no sea tan universalmente aplicable a la seguridad de los subprocesos como se sincronizada. El listado 1 muestra una clase de rango numérico no seguro de huella. Contiene un invariante: el límite inferior siempre es menor o igual al límite superior.
Use volátil correctamente:
Modo #1: banderas de estado
Quizás la especificación de implementar la variable volátil es simplemente usar un indicador de estado booleano para indicar que se ha producido un evento único importante, como completar la inicialización o solicitar el tiempo de inactividad.
Muchas aplicaciones incluyen una estructura de control en forma de "ejecutar algún trabajo cuando el programa no está listo para detenerse", como se muestra en el Listado 2:
Listado 2. Use la variable volátil como indicador de estado
Volátil booleano apagado; ... public void shutdown () {shutdownRequested = true; } public void dowork () {while (! shutdownRequested) {// hacer cosas}}Es muy probable que el método apagado () se llame desde fuera del bucle, es decir, en otro hilo, por lo que es necesario realizar algún tipo de sincronización para garantizar que la visibilidad de la variable de cierre de cierre se implementa correctamente. (Puede llamarse desde JMX Listener, Operation Listener en GUI Event Thread, a través de RMI, a través de un servicio web, etc.). Sin embargo, escribir un bucle con un bloque sincronizado es mucho más problemático que escribir con el indicador de estado volátil que se muestra en el Listado 2. Debido a que volátiles simplifica la codificación y los indicadores de estado no dependen de ningún otro estado dentro del programa, es muy adecuado para volátil aquí.
Una característica común de este tipo de etiqueta de estado es que generalmente solo hay una transición de estado; La bandera de cierre de cierre se convierte de falso en verdadero, y el programa se detiene. Este patrón se puede extender al indicador de estado de la transición de ida y vuelta, pero solo se puede extender si el período de transición no se nota (de falso a verdadero, entonces a falso). Además, se requieren algunos mecanismos de conversión de estado atómico, como variables atómicas.
Modo #2: publicación segura única
La falta de sincronización puede conducir a una visibilidad inalcadora, lo que hace que sea más difícil determinar cuándo escribir una referencia de objeto en lugar de un valor primitivo. En ausencia de sincronización, se puede encontrar un valor actualizado referenciado por un objeto (escrito por otro hilo) y el antiguo valor del estado de ese objeto existe simultáneamente. (Esta es la causa raíz del famoso problema de bloqueo de doble verificación donde las referencias de objetos se leen sin sincronización, lo que resulta en el problema de que puede ver una referencia actualizada, pero aún así ver un objeto construido incompletamente a través de esa referencia).
Una técnica para implementar una publicación segura de objetos es definir las referencias de objetos como tipo volátil. El listado 3 muestra un ejemplo en el que el subproceso de fondo carga algunos datos de la base de datos durante el inicio. Otros códigos, cuando pueden utilizar estos datos, verifican si se ha publicado antes de su uso.
Listado 3. Uso de la variable volátil para una versión segura única
Clase pública Backgroundfloobleloader {public volátil Floble thefloble; public void initinBackground () {// Haz muchas cosas thefloble = new flobowble (); // Esta es la única escritura en thefloble}} clase pública SomeTherClass {public void dowork () {while (true) {// haz algunas cosas ... // usa el floble, pero solo si está listo si (floobleloader.theflooble! = null) dosomething (floobleloader.heflooble);; }}}Si la referencia de Flooble no es un tipo volátil, el código en Dowork () obtendrá un floble construido de manera incompleta cuando no referente el flooble.
Una condición necesaria para este patrón es que el objeto publicado debe ser seguro de hilo o un objeto inmutable válido (efectivo inmutable significa que el estado del objeto nunca se modificará después de la publicación). Las referencias de tipo volátil aseguran la visibilidad del formulario de publicación del objeto, pero se requiere una sincronización adicional si el estado del objeto cambia después de la publicación.
Patrón #3: Observación independiente
Otro modo simple de usar volátilmente de forma segura es "liberar" las observaciones regularmente para el uso interno del programa. Por ejemplo, suponga que hay un sensor ambiental capaz de detectar la temperatura ambiente. Un hilo de fondo puede leer el sensor cada pocos segundos y actualizar la variable volátil que contiene el documento actual. Luego, otros hilos pueden leer esta variable para que puedan ver el último valor de temperatura en cualquier momento.
Otra aplicación que usa este modo es recopilar las estadísticas del programa. El Listado 4 muestra cómo el mecanismo de autenticación recuerda el nombre del usuario que inició sesión por última vez. Repita la referencia de LastUser para publicar valores para otras partes del programa.
Listado 4. Uso de la variable volátil para publicar múltiples observaciones independientes
clase pública usermanager {cadena volátil pública lastuser; Public Boolean Authenticate (String User, String Password) {Boolean Valid = PasswordIsValid (usuario, contraseña); if (válido) {user u = new User (); ActiveUsers.Add (U); LastUser = usuario; } retorno válido; }}Este modo es una extensión del modo anterior; Publicar un cierto valor para usar en otra parte del programa, pero a diferencia de publicar un evento único, es una serie de eventos independientes. Este patrón requiere que el valor publicado sea válido e inmutable, es decir, el estado del valor no cambiará después de la publicación. El código que usa este valor debe ser claro que el valor puede cambiar en cualquier momento.
Modo #4: modo "frijol volátil"
El patrón de frijoles volátiles es adecuado para marcos que usan Javabeans como una "estructura de honor". En el patrón de frijoles volátil, los javabeans se usan como un conjunto de contenedores con propiedades independientes de los métodos de Getter y/o Setter. El principio básico del patrón de frijol volátil es que muchos marcos proporcionan contenedores para los titulares de datos volátiles (como httpsession), pero los objetos colocados en estos contenedores deben ser seguros de subprocesos.
En el modo de frijol volátil, todos los miembros de datos de un Javabean son de tipo volátil, y los métodos de Getter y Setter deben ser muy ordinarios: no pueden contener ninguna lógica, excepto para obtener o establecer las propiedades correspondientes. Además, para los miembros de los datos referenciados por el objeto, el objeto referenciado debe ser válido e inmutable. (Esto prohibirá las propiedades con los valores de la matriz, porque cuando una referencia de matriz se declara como volátil, solo la referencia y no la matriz en sí tiene semántica volátil). Para cualquier variable volátil, los invariantes o restricciones no pueden contener propiedades de Javabean. Los ejemplos en el Listado 5 muestran a Javabeans que se adhieren al patrón de frijoles volátiles:
Modo #4: modo "frijol volátil"
@ThreadSafe Public Class Person {String volátil privado FirstName; Cadena volátil privada LastName; privado volátiles int Age; public String getFirstName () {return FirstName; } public String getLastName () {return LastName; } public int getAge () {return Age; } public void setFirstName (String FirstName) {this.firstname = firstName; } public void setLastName (String LastName) {this.lastName = lastName; } public void setAge (int Age) {this.age = edad; }}Modo avanzado de volátil
Los patrones descritos en las secciones anteriores cubren la mayoría de los casos de uso básicos, y el uso de volátiles en estos patrones es muy útil y simple. Esta sección introduce un modo más avanzado en el que Volátil proporcionará ventajas de rendimiento o escalabilidad.
Los modos avanzados de aplicaciones volátiles son muy frágiles. Por lo tanto, los supuestos deben probarse cuidadosamente, y estos patrones están estrictamente encapsulados porque incluso los cambios muy pequeños pueden corromper su código. Del mismo modo, la razón para usar un caso de uso volátil más avanzado es que puede mejorar el rendimiento, asegurando que realmente determine que necesita lograr este beneficio de rendimiento antes de comenzar a aplicar los patrones avanzados. Hay compensaciones en estos patrones, renunciando a la legibilidad o la mantenibilidad a cambio de posibles ganancias de rendimiento; si no necesita mejoras de rendimiento (o no puede demostrar que lo necesita a través de un programa de prueba estricto), es probable que sea un mal trato porque es probable que pierda menos dinero y obtenga algo que valga menos de lo que renuncia.
Modo #5: Estrategia de bloqueo de lectura-escritura con sobrecarga baja
Hasta ahora, debe entender que Volátil no es lo suficientemente capaz como para implementar contadores. Debido a que ++ X es en realidad una combinación simple de tres operaciones (leer, agregar, almacenar), si múltiples hilos intentan realizar operaciones incrementales en el mostrador volátil al mismo tiempo, su valor actualizado puede perderse.
Sin embargo, si la operación de lectura es mucho más que la operación de escritura, puede usar un bloqueo interno y variables volátiles para reducir la sobrecarga de la ruta del código público. Los contadores seguros de subprocesos que se muestran en el Listado 6 usan sincronizados para garantizar que las operaciones incrementales sean atómicas y volátiles para garantizar la visibilidad del resultado actual. Este método puede lograr un mejor rendimiento si las actualizaciones no son frecuentes, porque la sobrecarga de rutas de lectura implica solo la operación de lectura volátil, que generalmente es mejor que la sobrecarga de una adquisición de bloqueo sin competencia.
Listado 6. Use volátil y sincronizado para lograr "bloqueos de lectura de escritura de sobrecarga más baja"
@ThreadSafe Class Public CheesyCounter {// emplea el truco de bloqueo de lectura de lectura barata // Todas las operaciones mutativas deben realizarse con el bloqueo 'este' sostenido @GuardedBy ("this") privado intales volátiles int; public int getValue () {Valor de retorno; } public sincronizado int increment () {return value ++; }}La razón por la que esta técnica se llama "bloqueo de lectura de escritura sobre la cabeza inferior" es porque usa un mecanismo de sincronización diferente para las operaciones de lectura-escritura. Debido a que la operación de escritura en este ejemplo viola la primera condición de usar volátiles, el contador no se puede implementar de manera segura con volátil: debe usar un bloqueo. Sin embargo, puede usar volátiles en las operaciones de lectura para garantizar la visibilidad del valor actual, por lo que puede usar bloqueos para realizar todos los cambios y solo lectura con volátiles. Entre ellos, el bloqueo permite que solo un hilo acceda al valor a la vez, y Volátil permite que múltiples subprocesos realicen operaciones de lectura. Por lo tanto, cuando se usa Volatile para asegurarse de que se lea la ruta del código, es más compartir que usar el bloqueo para ejecutar todas las rutas de código, al igual que una operación de lectura -escritura. Sin embargo, tenga en cuenta las debilidades de este modelo en mente: si la aplicación más básica de este modelo está más allá, será muy difícil combinar estos dos mecanismos de sincronización competidores.
Sobre el reordenamiento de instrucciones y la regla de Hogar-Before
1. Deja reordenar
La especificación del lenguaje Java estipula que el hilo JVM mantiene la semántica secuencial internamente, es decir, siempre que el resultado final del programa sea equivalente a sus resultados en un entorno secuencial estricto, el orden de ejecución de instrucciones puede ser inconsistente con el orden del código. Este proceso es reordenado por un comando. La importancia del reordenamiento de instrucciones es que el JVM puede reordenar adecuadamente las instrucciones de la máquina de acuerdo con las características del procesador (sistema de caché de niveles múltiples de CPU, procesador de múltiples núcleos, etc.), de modo que las instrucciones de la máquina estén más en línea con las características de ejecución de la CPU y maximice el rendimiento de la máquina.
El modelo más simple para la ejecución del programa es ejecutar en el orden en el que aparecen las instrucciones, que es independiente de la CPU que ejecuta las instrucciones, asegurando la portabilidad de las instrucciones en la mayor medida. El término profesional para este modelo se llama modelo de consistencia secuencial. Sin embargo, los sistemas informáticos modernos y las arquitecturas del procesador no garantizan esto (porque la designación artificial no siempre puede garantizar el cumplimiento de las características de procesamiento de CPU).
2. APPENS-BORE REGLA
El modelo de almacenamiento de Java tiene un principio antes, es decir, si la acción B quiere ver el resultado de la ejecución de la acción A (independientemente de si A/B se ejecuta en el mismo hilo), entonces A/B necesita satisfacer la relación de antes.
Antes de introducir la regla de sucesión antes, introducir un concepto: acción JMM (acción del modelo de memeory Java), Java almacena acciones del modelo. Una acción incluye: leer y escribir variables, bloquear y liberar bloqueos de liberación, start start () y unir (). La cerradura se mencionará más tarde.
sucede antes de las reglas completas:
(1) Cada acción en el mismo hilo ocurre antes de cualquier acción que aparezca después de ella.
(2) Desbloquear un monitor ocurre antes de cada bloqueo posterior en el mismo monitor.
(3) La operación de escritura en el campo volátil se produce antes de cada operación de lectura posterior del mismo campo.
(4) La llamada a Thread.Start () ocurrirá antes de las acciones en el hilo de inicio.
(5) Todas las acciones en el hilo ocurren antes de verificar otros hilos para finalizar este hilo o regresar en Thread.Join () o Thread.Isalive () == False.
(6) Un hilo A llama a la interrupción () de otro hilo B ocurre antes de que el hilo A encuentre que B se ve interrumpido por A (B lanza una excepción o A Detects B es Interrupted () o interrumpido ()).
(7) El final de un constructor de objeto ocurre antes y el comienzo del finalizador del objeto
(8) Si una acción ocurre, antes de la acción B, y la acción B ocurre antes y la acción, entonces una acción ocurre antes de la acción C.
Lo anterior se trata de este artículo. Te lo presentaré aquí. Espero que sea útil para usted aprender y comprender las variables volátiles en Java.