¿Cómo funcionan internamente atómico / volátil / sincronizado?
¿Cuál es la diferencia entre los siguientes bloques de código?
Código 1
private int counter;
public int getNextUniqueIndex() {
return counter++;
}
Código 2
private AtomicInteger counter;
public int getNextUniqueIndex() {
return counter.getAndIncrement();
}
Código 3
private volatile int counter;
public int getNextUniqueIndex() {
return counter++;
}
¿ volatile
Funciona de la siguiente manera? Es
volatile int i = 0;
void incIBy5() {
i += 5;
}
equivalente a
Integer i = 5;
void incIBy5() {
int temp;
synchronized(i) { temp = i }
synchronized(i) { i = temp + 5 }
}
Creo que dos hilos no pueden entrar en un bloque sincronizado al mismo tiempo ... ¿estoy en lo cierto? Si esto es cierto, ¿cómo atomic.incrementAndGet()
funciona sin él synchronized
? ¿Y es seguro para subprocesos?
¿Y cuál es la diferencia entre lectura interna y escritura en variables volátiles / variables atómicas? Leí en algún artículo que el hilo tiene una copia local de las variables, ¿qué es eso?
Respuestas:
Usted pregunta específicamente cómo funcionan internamente , así que aquí está:
Sin sincronización
Básicamente lee el valor de la memoria, lo incrementa y lo devuelve a la memoria. Esto funciona en un solo subproceso, pero hoy en día, en la era de los cachés de varios núcleos, múltiples CPU y niveles múltiples, no funcionará correctamente. En primer lugar, presenta la condición de carrera (varios hilos pueden leer el valor al mismo tiempo), pero también problemas de visibilidad. El valor solo puede almacenarse en la memoria de la CPU " local " (algo de caché) y no ser visible para otras CPU / núcleos (y, por lo tanto, subprocesos). Es por eso que muchos se refieren a la copia local de una variable en un hilo. Es muy inseguro. Considere este popular pero roto código de detención de subprocesos:
Agregue
volatile
a lastopped
variable y funciona bien: si cualquier otro hilo modifica lastopped
variable a través delpleaseStop()
método, se garantiza que verá ese cambio inmediatamente en elwhile(!stopped)
bucle del hilo de trabajo . Por cierto, esta no es una buena manera de interrumpir un hilo tampoco, vea: Cómo detener un hilo que se ejecuta para siempre sin ningún uso y Detener un hilo java específico .AtomicInteger
La
AtomicInteger
clase usa operaciones de CPU de bajo nivel CAS ( comparar e intercambiar ) (¡no se necesita sincronización!) Le permiten modificar una variable particular solo si el valor presente es igual a otra cosa (y se devuelve con éxito). Entonces, cuando lo ejecutas,getAndIncrement()
en realidad se ejecuta en un bucle (implementación real simplificada):Básicamente: lee; intente almacenar valor incrementado; si no tiene éxito (el valor ya no es igual a
current
), lea e intente nuevamente. ElcompareAndSet()
se implementa en código nativo (ensamblado).volatile
sin sincronizaciónEste código no es correcto Soluciona el problema de visibilidad (
volatile
se asegura de que otros hilos puedan ver los cambios realizadoscounter
) pero aún tiene una condición de carrera. Esto se ha explicado varias veces: el incremento previo / posterior no es atómico.El único efecto secundario
volatile
es el " vaciado " de cachés para que todas las demás partes vean la versión más reciente de los datos. Esto es demasiado estricto en la mayoría de las situaciones; Por esovolatile
no es por defecto.volatile
sin sincronización (2)El mismo problema que el anterior, pero aún peor porque
i
no lo esprivate
. La condición de carrera aún está presente. Por qué es un problema? Si, digamos, dos hilos ejecutan este código simultáneamente, el resultado podría ser+ 5
o+ 10
. Sin embargo, tiene la garantía de ver el cambio.Múltiple independiente
synchronized
Sorpresa, este código también es incorrecto. De hecho, está completamente equivocado. En primer lugar, está sincronizando
i
, lo que está a punto de cambiar (además,i
es primitivo, así que supongo que está sincronizando en un temporalInteger
creado mediante autoboxing ...) Completamente defectuoso. También podrías escribir:No pueden entrar dos hilos en el mismo
synchronized
bloque con el mismo bloqueo . En este caso (y de manera similar en su código) el objeto de bloqueo cambia con cada ejecución, por lo quesynchronized
efectivamente no tiene ningún efecto.Incluso si ha utilizado una variable final (o
this
) para la sincronización, el código sigue siendo incorrecto. Dos hilos pueden leer primeroi
de formatemp
síncrona (con el mismo valor localmentetemp
), luego el primero asigna un nuevo valor ai
(digamos, del 1 al 6) y el otro hace lo mismo (del 1 al 6).La sincronización debe abarcar desde la lectura hasta la asignación de un valor. Su primera sincronización no tiene efecto (la lectura de un
int
es atómica) y la segunda también. En mi opinión, estas son las formas correctas:fuente
compareAndSet
es solo una delgada envoltura alrededor de la operación CAS. Entro en algunos detalles en mi respuesta.Declarar una variable como volátil significa que modificar su valor afecta inmediatamente el almacenamiento de memoria real para la variable. El compilador no puede optimizar ninguna referencia hecha a la variable. Esto garantiza que cuando un hilo modifica la variable, todos los otros hilos ven el nuevo valor inmediatamente. (Esto no está garantizado para las variables no volátiles).
La declaración de una variable atómica garantiza que las operaciones realizadas en la variable se realicen de manera atómica, es decir, que todos los subpasos de la operación se completen dentro del hilo que se ejecutan y no sean interrumpidos por otros hilos. Por ejemplo, una operación de incremento y prueba requiere que la variable se incremente y luego se compare con otro valor; Una operación atómica garantiza que estos dos pasos se completarán como si fueran una sola operación indivisible / ininterrumpible.
La sincronización de todos los accesos a una variable permite que solo un subproceso a la vez acceda a la variable, y obliga a todos los demás subprocesos a esperar que ese subproceso de acceso libere su acceso a la variable.
El acceso sincronizado es similar al acceso atómico, pero las operaciones atómicas generalmente se implementan en un nivel inferior de programación. Además, es completamente posible sincronizar solo algunos accesos a una variable y permitir que otros accesos no estén sincronizados (por ejemplo, sincronizar todas las escrituras a una variable pero ninguna de las lecturas de la misma).
La atomicidad, la sincronización y la volatilidad son atributos independientes, pero generalmente se usan en combinación para forzar una cooperación de hilos adecuada para acceder a las variables.
Anexo (abril de 2016)
El acceso sincronizado a una variable generalmente se implementa mediante un monitor o semáforo . Estos son mecanismos mutex de bajo nivel (exclusión mutua) que permiten que un hilo adquiera el control de una variable o bloque de código exclusivamente, obligando a todos los otros hilos a esperar si también intentan adquirir el mismo mutex. Una vez que el subproceso propietario libera el mutex, otro subproceso puede adquirir el mutex a su vez.
Anexo (julio de 2016)
La sincronización ocurre en un objeto . Esto significa que llamar a un método sincronizado de una clase bloqueará el
this
objeto de la llamada. Los métodos estáticos sincronizados bloquearán elClass
objeto en sí.Del mismo modo, ingresar un bloque sincronizado requiere bloquear el
this
objeto del método.Esto significa que un método sincronizado (o bloque) puede ejecutarse en múltiples subprocesos al mismo tiempo si se bloquean en diferentes objetos, pero solo un subproceso puede ejecutar un método sincronizado (o bloque) a la vez para un solo objeto dado .
fuente
volátil:
volatile
es una palabra clavevolatile
obliga a todos los subprocesos a obtener el último valor de la variable de la memoria principal en lugar de la memoria caché. No se requiere bloqueo para acceder a variables volátiles. Todos los hilos pueden acceder al valor variable volátil al mismo tiempo.El uso de
volatile
variables reduce el riesgo de errores de consistencia de la memoria, porque cualquier escritura en una variable volátil establece una relación de suceder antes con lecturas posteriores de esa misma variable.Esto significa que los cambios en una
volatile
variable siempre son visibles para otros subprocesos . Además, también significa que cuando un hilo lee unavolatile
variable, ve no solo el último cambio en el volátil, sino también los efectos secundarios del código que condujo al cambio .Cuándo usar: Un hilo modifica los datos y otros hilos tienen que leer el último valor de los datos. Otros hilos tomarán alguna acción, pero no actualizarán los datos .
AtomicXXX:
AtomicXXX
Las clases admiten la programación segura y sin subprocesos en variables individuales EstasAtomicXXX
clases (comoAtomicInteger
) resuelven errores de inconsistencia de memoria / efectos secundarios de modificación de variables volátiles, a las que se ha accedido en múltiples subprocesos.Cuándo usar: múltiples hilos pueden leer y modificar datos.
sincronizado:
synchronized
es una palabra clave utilizada para proteger un método o bloque de código. Al hacer que el método esté sincronizado tiene dos efectos:Primero, no es posible
synchronized
intercalar dos invocaciones de métodos en el mismo objeto. Cuando un subproceso está ejecutando unsynchronized
método para un objeto, todos los demás subprocesos que invocansynchronized
métodos para el mismo objeto bloquean (suspenden la ejecución) hasta que el primer subproceso se realiza con el objeto.En segundo lugar, cuando
synchronized
sale un método, establece automáticamente una relación de suceso anterior con cualquier invocación posterior de unsynchronized
método para el mismo objeto. Esto garantiza que los cambios en el estado del objeto sean visibles para todos los hilos.Cuándo usar: múltiples hilos pueden leer y modificar datos. Su lógica empresarial no solo actualiza los datos sino que también ejecuta operaciones atómicas
AtomicXXX
es equivalentevolatile + synchronized
a pesar de que la implementación es diferente.AmtomicXXX
extiende lasvolatile
variables +compareAndSet
métodos pero no usa sincronización.Preguntas SE relacionadas:
Diferencia entre volátil y sincronizado en Java
Boolean volátil vs AtomicBoolean
Buenos artículos para leer: (El contenido anterior se toma de estas páginas de documentación)
https://docs.oracle.com/javase/tutorial/essential/concurrency/sync.html
https://docs.oracle.com/javase/tutorial/essential/concurrency/atomic.html
https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/package-summary.html
fuente
Dos hilos no pueden ingresar un bloque sincronizado en el mismo objeto dos veces. Esto significa que dos hilos pueden entrar en el mismo bloque en diferentes objetos. Esta confusión puede conducir a un código como este.
Esto no se comportará como se esperaba, ya que podría bloquearse en un objeto diferente cada vez.
si. No utiliza bloqueo para lograr la seguridad del hilo.
Si desea saber cómo funcionan con más detalle, puede leer el código para ellos.
La clase atómica usa campos volátiles . No hay diferencia en el campo. La diferencia son las operaciones realizadas. Las clases atómicas usan operaciones CompareAndSwap o CAS.
Solo puedo suponer que se refiere al hecho de que cada CPU tiene su propia vista de memoria en caché que puede ser diferente de cualquier otra CPU. Para garantizar que su CPU tenga una vista coherente de los datos, debe utilizar técnicas de seguridad de subprocesos.
Esto es solo un problema cuando la memoria se comparte, al menos un subproceso la actualiza.
fuente
Sincronizado Vs Atómico Vs Volátil:
Por favor corrígeme si algo me perdí.
fuente
Una sincronización volátil + es una solución infalible para que una operación (declaración) sea completamente atómica, lo que incluye múltiples instrucciones para la CPU.
Digamos por ejemplo: volátil int i = 2; i ++, que no es más que i = i + 1; lo que hace que i sea el valor 3 en la memoria después de la ejecución de esta declaración. Esto incluye leer el valor existente de la memoria para i (que es 2), cargar en el registro del acumulador de la CPU y hacer el cálculo incrementando el valor existente con uno (2 + 1 = 3 en el acumulador) y luego escribir ese valor incrementado De vuelta a la memoria. Estas operaciones no son lo suficientemente atómicas, aunque el valor de i es volátil. ser volátil garantiza solo que una SOLA lectura / escritura desde la memoria es atómica y no MÚLTIPLE. Por lo tanto, necesitamos estar sincronizados también alrededor de i ++ para mantenerlo como una declaración atómica infalible. Recuerde el hecho de que una declaración incluye múltiples declaraciones.
Espero que la explicación sea lo suficientemente clara.
fuente
El modificador volátil Java es un ejemplo de un mecanismo especial para garantizar que la comunicación ocurra entre hilos. Cuando un hilo escribe en una variable volátil, y otro hilo ve esa escritura, el primer hilo le dice al segundo todo el contenido de la memoria hasta que realiza la escritura en esa variable volátil.
Las operaciones atómicas se realizan en una sola unidad de tarea sin interferencia de otras operaciones. Las operaciones atómicas son necesarias en un entorno de subprocesos múltiples para evitar inconsistencias de datos.
fuente