En el procesamiento concurrente de subprocesos de Java, actualmente existe mucha confusión sobre el uso de la palabra clave volátil. Se cree que al utilizar esta palabra clave, todo estará bien al realizar el procesamiento concurrente de subprocesos múltiples.
El lenguaje Java admite subprocesos múltiples. Para resolver el problema de la concurrencia de subprocesos, se introducen dentro del lenguaje el bloque sincronizado y los mecanismos de palabras clave volátiles.
sincronizado
Todo el mundo está familiarizado con los bloques sincronizados, que se implementan mediante la palabra clave sincronizada. Con la adición de declaraciones sincronizadas y de bloque, cuando varios subprocesos acceden a ellos, solo un subproceso puede utilizar métodos modificados sincronizados o bloques de código al mismo tiempo.
volátil
Para las variables modificadas con volátil, cada vez que el hilo use la variable, leerá el valor modificado de la variable. Los volátiles pueden utilizarse indebidamente fácilmente para realizar operaciones atómicas.
Veamos un ejemplo a continuación. Implementamos un contador Cada vez que se inicia el hilo, se llamará al método counter inc para agregar uno al contador.
Entorno de ejecución: versión jdk: jdk1.6.0_31, memoria: 3G CPU: x86 2.4G
Copie el código de código de la siguiente manera:
Contador de clase pública {
recuento de int estático público = 0;
público estático vacío inc() {
//Retrasa 1 milisegundo aquí para que el resultado sea obvio
intentar {
Hilo.dormir(1);
} captura (Excepción interrumpida e) {
}
contar++;
}
público estático vacío principal (String [] argumentos) {
// Inicie 1000 subprocesos al mismo tiempo para realizar cálculos de i++ y ver los resultados reales
para (int i = 0; i < 1000; i++) {
nuevo hilo (nuevo ejecutable() {
@Anular
ejecución pública vacía() {
Contador.inc();
}
}).comenzar();
}
//El valor aquí puede ser diferente cada vez que se ejecuta, posiblemente 1000
System.out.println("Resultado de la ejecución: Counter.count=" + Counter.count);
}
}
Resultado de ejecución: Counter.count=995
El resultado real de la operación puede ser diferente cada vez. El resultado de esta máquina es: resultado de ejecución: Counter.count = 995. Se puede ver que en un entorno de subprocesos múltiples, Counter.count no espera que el resultado sea 1000.
Mucha gente piensa que se trata de un problema de concurrencia de subprocesos múltiples. Solo necesita agregar volátiles antes del recuento de variables para evitar este problema. Luego modificaremos el código para ver si el resultado cumple con nuestras expectativas.
Copie el código de código de la siguiente manera:
Contador de clase pública {
recuento de int estático volátil público = 0;
público estático vacío inc() {
//Retrasa 1 milisegundo aquí para que el resultado sea obvio
intentar {
Hilo.dormir(1);
} captura (Excepción interrumpida e) {
}
contar++;
}
público estático vacío principal (String [] argumentos) {
// Inicie 1000 subprocesos al mismo tiempo para realizar cálculos de i++ y ver los resultados reales
para (int i = 0; i < 1000; i++) {
nuevo hilo (nuevo ejecutable() {
@Anular
ejecución pública vacía() {
Contador.inc();
}
}).comenzar();
}
//El valor aquí puede ser diferente cada vez que se ejecuta, posiblemente 1000
System.out.println("Resultado de la ejecución: Counter.count=" + Counter.count);
}
}
Resultado de ejecución: Counter.count=992
El resultado de ejecución todavía no es 1000 como esperábamos. Analicemos las razones a continuación.
En el artículo Java Garbage Collection, se describe la asignación de memoria durante el tiempo de ejecución de JVM. Una de las áreas de memoria es la pila de la máquina virtual jvm. Cada subproceso tiene una pila de subprocesos cuando se está ejecutando. La pila de subprocesos almacena información de valor variable cuando el subproceso se está ejecutando. Cuando un hilo accede al valor de un objeto, primero encuentra el valor de la variable correspondiente a la memoria del montón a través de la referencia del objeto y luego carga el valor específico de la variable de memoria del montón en la memoria local del hilo para crear una copia del variable. Después de eso, el hilo no tiene nada que ver con el valor de la variable del objeto en la memoria del montón. En cambio, modifica directamente el valor de la variable de copia en un momento determinado después de la modificación (antes de que salga el hilo). el valor de la copia de la variable del hilo se vuelve a escribir automáticamente en la variable del objeto en el montón. De esta forma, el valor del objeto en el montón cambia. La imagen de abajo
Describe esta interacción
leer y cargar copias de variables desde la memoria principal a la memoria de trabajo actual
usar y asignar código de ejecución y cambiar valores de variables compartidas
almacenar y escribir, actualizar contenido relacionado con la memoria principal con datos de la memoria de trabajo
donde usar y asignar pueden aparecer varias veces
Sin embargo, estas operaciones no son atómicas, es decir, después de la carga de lectura, si se modifica la variable de recuento de la memoria principal, el valor en la memoria de trabajo del subproceso no cambiará porque ya se ha cargado, por lo que el resultado calculado será el esperado. lo mismo
Para variables modificadas volátiles, la máquina virtual JVM solo garantiza que el valor cargado desde la memoria principal a la memoria de trabajo del subproceso sea el último
Por ejemplo, si el subproceso 1 y el subproceso 2 encuentran que el valor de recuento en la memoria principal es 5 durante las operaciones de lectura y carga, cargarán el último valor.
Después de modificar el recuento de montón del subproceso 1, se escribirá en la memoria principal y la variable de recuento en la memoria principal pasará a ser 6.
Dado que el subproceso 2 ya realizó operaciones de lectura y carga, después de realizar la operación, también actualizará el valor de la variable de recuento de memoria principal a 6.
Como resultado, después de que dos subprocesos se modifiquen en el tiempo utilizando la palabra clave volátil, seguirá habiendo concurrencia.