¿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++;
}
¿ volatileFunciona 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
volatilea lastoppedvariable y funciona bien: si cualquier otro hilo modifica lastoppedvariable 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 .AtomicIntegerLa
AtomicIntegerclase 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).volatilesin sincronizaciónEste código no es correcto Soluciona el problema de visibilidad (
volatilese 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
volatilees 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 esovolatileno es por defecto.volatilesin sincronización (2)El mismo problema que el anterior, pero aún peor porque
ino 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+ 5o+ 10. Sin embargo, tiene la garantía de ver el cambio.Múltiple independiente
synchronizedSorpresa, 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,ies primitivo, así que supongo que está sincronizando en un temporalIntegercreado mediante autoboxing ...) Completamente defectuoso. También podrías escribir:No pueden entrar dos hilos en el mismo
synchronizedbloque 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 quesynchronizedefectivamente 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 primeroide formatempsí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
intes atómica) y la segunda también. En mi opinión, estas son las formas correctas:fuente
compareAndSetes 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
thisobjeto de la llamada. Los métodos estáticos sincronizados bloquearán elClassobjeto en sí.Del mismo modo, ingresar un bloque sincronizado requiere bloquear el
thisobjeto 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:
volatilees una palabra clavevolatileobliga 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
volatilevariables 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
volatilevariable siempre son visibles para otros subprocesos . Además, también significa que cuando un hilo lee unavolatilevariable, 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:
AtomicXXXLas clases admiten la programación segura y sin subprocesos en variables individuales EstasAtomicXXXclases (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:
synchronizedes 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
synchronizedintercalar dos invocaciones de métodos en el mismo objeto. Cuando un subproceso está ejecutando unsynchronizedmétodo para un objeto, todos los demás subprocesos que invocansynchronizedmétodos para el mismo objeto bloquean (suspenden la ejecución) hasta que el primer subproceso se realiza con el objeto.En segundo lugar, cuando
synchronizedsale un método, establece automáticamente una relación de suceso anterior con cualquier invocación posterior de unsynchronizedmé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
AtomicXXXes equivalentevolatile + synchronizeda pesar de que la implementación es diferente.AmtomicXXXextiende lasvolatilevariables +compareAndSetmé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