explicacion - synchronized java
Volatile Vs Atomic (6)
Esta pregunta ya tiene una respuesta aquí:
Leo en algún lugar debajo de la línea.
La palabra clave volátil de Java no significa atómica, es un concepto erróneo común de que después de declarar la operación volátil,
++
será atómica, para que la operación sea atómica aún necesita garantizar el acceso exclusivo mediante el uso desynchronized
método o bloquesynchronized
en Java.
Entonces, ¿qué pasará si dos hilos atacan una variable primitiva volatile
al mismo tiempo?
¿Significa esto que quienquiera que la prenda, primero establecerá su valor? Y si, mientras tanto, aparece algún otro subproceso y lee el valor antiguo mientras el primer subproceso cambiaba su valor, entonces ¿el nuevo subproceso no leerá su valor anterior?
¿Cuál es la diferencia entre Atomic y la palabra clave volátil?
Entonces, ¿qué pasará si dos hilos atacan una variable primitiva volátil al mismo tiempo?
Por lo general, cada uno puede incrementar el valor. Sin embargo, en algún momento, ambos actualizarán el valor al mismo tiempo y, en lugar de incrementar en un total de 2, ambos suben el incremento en 1 y solo se agrega 1.
¿Significa esto que quienquiera que la prenda, primero establecerá su valor?
No hay bloqueo Para eso está synchronized
.
Y si entre tanto, aparece algún otro subproceso y lee el valor antiguo mientras el primer subproceso cambiaba su valor, entonces ¿el nuevo subproceso no leerá su valor anterior?
Sí,
¿Cuál es la diferencia entre Atomic y la palabra clave volátil?
AtomicXxxx envuelve un elemento volátil por lo que son básicamente los mismos, la diferencia es que proporciona operaciones de nivel superior como CompareAndSwap que se utiliza para implementar el incremento.
AtomicXxxx también es compatible con lazySet. Esto es como un conjunto volátil, pero no detiene el proceso esperando a que se complete la escritura. Puede significar que si lee un valor que acaba de escribir, es posible que vea el valor anterior, pero no debería hacerlo de todos modos. La diferencia es que configurar un valor volátil toma alrededor de 5 ns, bit lazySet toma alrededor de 0,5 ns.
Como Trying como se indica, volatile
solo se ocupa de la visibilidad.
Considere este fragmento en un entorno concurrente:
boolean isStopped = false;
:
:
while (!isStopped) {
// do some kind of work
}
La idea aquí es que algún hilo podría cambiar el valor de isStopped
de falso a verdadero para indicar al ciclo siguiente que es hora de detener el bucle.
Intuitivamente, no hay problema. Lógicamente, si otro hilo hace que isStopped
sea igual a verdadero, entonces el ciclo debe terminar. La realidad es que el ciclo probablemente nunca terminará aunque otro hilo haga que isStopped
sea igual a verdadero.
La razón para esto no es intuitiva, pero considere que los procesadores modernos tienen múltiples núcleos y que cada núcleo tiene múltiples registros y múltiples niveles de memoria caché que no son accesibles para otros procesadores . En otras palabras, los valores que se almacenan en caché en la memoria local de un procesador no son visibles para los hilos que se ejecutan en un procesador diferente. Aquí radica uno de los problemas centrales de concurrencia: visibilidad.
El Modelo de memoria Java no garantiza en absoluto cuándo los cambios que se realizan en una variable en el hilo pueden ser visibles para otros hilos. Para garantizar que las actualizaciones sean visibles tan pronto como estén hechas, debe sincronizar.
La palabra clave volatile
es una forma débil de sincronización. Si bien no hace nada por la exclusión mutua o la atomicidad, proporciona una garantía de que los cambios realizados en una variable en un subproceso serán visibles para otros subprocesos tan pronto como se realice. Debido a que las lecturas y escrituras individuales para variables que no son de 8 bytes son atómicas en Java, declarar las variables volatile
proporciona un mecanismo fácil para proporcionar visibilidad en situaciones donde no existen otros requisitos de atomicidad o exclusión mutua.
El efecto de la palabra clave volatile
es aproximadamente que cada operación individual de lectura o escritura en esa variable es atómica.
Notablemente, sin embargo, una operación que requiere más de una lectura / escritura, como i++
, que es equivalente a i = i + 1
, que hace una lectura y una escritura, no es atómica, ya que otro hilo puede escribirle a entre la lectura y la escritura.
Las clases Atomic
, como AtomicInteger
y AtomicReference
, proporcionan una variedad más amplia de operaciones de forma atómica, incluyendo específicamente el incremento para AtomicInteger
.
Hay dos conceptos importantes en el entorno de multihilo.
- atomicidad
- visibilidad
Volatile
erradica el problema de visibilidad pero no se ocupa de la atomicidad. Volatile
evitará que el compilador reordene la instrucción que implica la escritura y la posterior lectura de una variable volátil. por ejemplo, k++
Aquí k++
no es una instrucción de máquina individual, sino tres instrucciones de la máquina.
- copiar el valor para registrarse
- incrementarlo
- colocarlo de nuevo
Por lo tanto, aunque declare variable a volatile
, no hará que esta operación sea atómica, lo que significa que otro subproceso puede ver un resultado intermedio que es un valor obsoleto o no deseado para el otro subproceso.
Pero AtomicInteger
, AtomicReference
se basa en las instrucciones Compare y Swap . CAS tiene tres operandos: una ubicación de memoria V
sobre la cual operar, el valor antiguo esperado A
y el nuevo valor B
CAS
actualiza atómicamente V
al nuevo valor B
, pero solo si el valor en V
coincide con el valor A
esperado anterior; de lo contrario, no hace nada. En cualquier caso, devuelve el valor actualmente en V
Esto es utilizado por JVM en AtomicInteger
, AtomicReference
y llaman a la función como compareAndSet()
. Si el procesador subyacente no admite esta funcionalidad, JVM la implementa mediante bloqueo por rotación .
La palabra clave volatile
se usa:
- para hacer que las operaciones no atómicas de 64 bits sean atómicas:
long
ydouble
. (todos los demás accesos primitivos ya están garantizados como atómicos) - hacer actualizaciones variables garantizadas para ser vistas por otros hilos + efectos de visibilidad: después de escribir en una variable volátil, todas las variables visibles antes de escribir esa variable se vuelven visibles para otro hilo después de leer la misma variable volátil (suceder antes del pedido).
Las clases java.util.concurrent.atomic.*
Son, de acuerdo con los documentos java :
Un pequeño conjunto de herramientas de clases que admiten la programación sin bloqueo de subprocesos en variables individuales. En esencia, las clases de este paquete extienden la noción de valores volátiles, campos y elementos de matriz a aquellos que también proporcionan una operación de actualización condicional atómica del formulario:
boolean compareAndSet(expectedValue, updateValue);
Las clases atómicas se basan en la función atómica compareAndSet(...)
que se asigna a una instrucción de CPU atómica. Las clases atómicas introducen lo que sucede antes de ordenar como lo hacen las variables volatile
. (con una excepción: weakCompareAndSet(...)
).
De los documentos de java:
Cuando un hilo ve una actualización de una variable atómica causada por un weakCompareAndSet, no necesariamente ve actualizaciones a otras variables que ocurrieron antes del weakCompareAndSet.
Para su pregunta:
¿Significa esto que quienquiera que la prenda, primero establecerá su valor? Y si entre tanto, aparece algún otro subproceso y lee el valor antiguo mientras el primer subproceso cambiaba su valor, entonces ¿el nuevo subproceso no leerá su valor anterior?
No bloquea nada, lo que está describiendo es una condición de carrera típica que sucederá eventualmente si los subprocesos acceden a los datos compartidos sin una sincronización adecuada. Como ya se mencionó, declarar una variable volatile
en este caso solo asegurará que otros subprocesos verán el cambio de la variable (el valor no se almacenará en caché en un registro de alguna memoria caché que solo se ve en una cadena).
¿Cuál es la diferencia entre
AtomicInteger
yvolatile int
?
AtomicInteger
proporciona operaciones atómicas en un int
con una sincronización adecuada (por ejemplo, incrementAndGet()
, getAndAdd(...)
, ...), volatile int
solo asegurará la visibilidad del int
a otros hilos.
Volátil y atómico son dos conceptos diferentes. Volátil asegura que cierto estado esperado (de memoria) es verdadero en diferentes subprocesos, mientras que Atomics asegura que el funcionamiento en variables se realiza atómicamente.
Tome el siguiente ejemplo de dos hilos en Java:
Tema A:
value = 1;
done = true;
Hilo B:
if (done)
System.out.println(value);
Comenzando con value = 0
y done = false
la regla de threading nos dice que no está definido si el Thread B imprimirá o no el valor. ¡Además, el valor no está definido en ese punto también! Para explicar esto, necesita saber un poco sobre la gestión de memoria Java (que puede ser compleja), en resumen: los subprocesos pueden crear copias locales de variables, y la JVM puede reordenar el código para optimizarlo, por lo tanto, no hay garantía de que el código anterior se ejecuta exactamente en ese orden. El ajuste hecho a verdadero y luego establecer el valor en 1 podría ser un posible resultado de las optimizaciones de JIT.
volatile
solo garantiza que, en el momento del acceso de dicha variable, el nuevo valor será inmediatamente visible para todos los demás subprocesos y el orden de ejecución garantiza que el código se encuentre en el estado que cabría esperar. Entonces, en el caso del código anterior, definir done
como volátil asegurará que cada vez que el Tema B verifique la variable, sea falso o verdadero, y si es verdadero, el value
se ha establecido en 1.
Como efecto secundario de la volatilidad , el valor de dicha variable se establece de manera atómica en todo el subproceso (a un costo muy bajo de la velocidad de ejecución). Sin embargo, esto solo es importante en los sistemas de 32 bits que utilizan variables largas (de 64 bits) (o similares), en la mayoría de los casos la configuración / lectura de una variable es atómica. Pero hay una diferencia importante entre un acceso atómico y una operación atómica. Volátil solo asegura que el acceso es atómico, mientras que Atomics garantiza que la operación sea atómica.
Toma el siguiente ejemplo:
i = i + 1;
No importa cómo defina i, un subproceso diferente leyendo el valor justo cuando se ejecuta la línea anterior podría obtener i, o i + 1, porque la operación no es atómica. Si el otro subproceso establece i en un valor diferente, en el peor de los casos, podría volver a establecerlo como antes por el subproceso A, porque estaba justo en el medio de calcular i + 1 en función del valor anterior, y luego establecer i de nuevo a ese viejo valor + 1. Explicación:
Assume i = 0
Thread A reads i, calculates i+1, which is 1
Thread B sets i to 1000 and returns
Thread A now sets i to the result of the operation, which is i = 1
Atomics como AtomicInteger aseguran que tales operaciones ocurren atómicamente. Entonces, el problema anterior no puede suceder, yo sería 1000 o 1001 una vez que ambos hilos hayan terminado.