que - text panel java
¿Cuál es la diferencia entre atómico/volátil/sincronizado? (7)
Sé que dos hilos no pueden entrar en el bloque Sincronizar al mismo tiempo
Dos hilos no pueden entrar dos veces en un bloque sincronizado en el mismo objeto. Esto significa que dos hilos pueden entrar en el mismo bloque en diferentes objetos. Esta confusión puede llevar a código como este.
private Integer i = 0;
synchronized(i) {
i++;
}
Esto no se comportará como se espera ya que podría bloquearse en un objeto diferente cada vez.
Si esto es cierto, ¿cómo funciona este atomic.incrementAndGet () sin sincronizar? y es hilo seguro?
sí. No utiliza el bloqueo para lograr la seguridad del hilo.
Si desea saber cómo funcionan con más detalle, puede leer el código por ellos.
¿Y cuál es la diferencia entre la lectura interna y la escritura en Variable volátil / Variable atómica?
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 las operaciones CompareAndSwap o CAS.
Leí en algún artículo que el hilo tiene copia local de variables ¿qué es eso?
Solo puedo asumir que se refiere al hecho de que cada CPU tiene su propia vista en caché de la memoria que puede ser diferente de todas las demás CPU. Para asegurarse de que su CPU tenga una visión 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 hilo la actualiza.
¿Cómo funciona internamente el 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
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 ... ¿tengo razón? Si esto es cierto, ¿cómo funciona atomic.incrementAndGet()
sin synchronized
? ¿Y es hilo seguro?
¿Y cuál es la diferencia entre lectura interna y escritura a 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?
Declarar una variable como volátil significa que la modificación de su valor afecta inmediatamente al almacenamiento de memoria real para la variable. El compilador no puede optimizar las referencias hechas a la variable. Esto garantiza que cuando un hilo modifica la variable, todos los demás 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 producen de forma atómica, es decir, que todos los subpasos de la operación se completan dentro del subproceso que se ejecutan y no son interrumpidos por otros subprocesos. 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 a que el 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 más bajo de programación. Además, es totalmente 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 imponer la cooperación de subprocesos adecuada para acceder a las variables.
Addendum (abril 2016)
El acceso sincronizado a una variable generalmente se implementa utilizando un monitor o un semáforo . Estos son mecanismos de exclusión mutua (exclusión mutua) de bajo nivel que permiten que un subproceso adquiera el control de una variable o bloque de código exclusivamente, obligando a todos los otros subprocesos a esperar si también intentan adquirir el mismo mutex. Una vez que el hilo propietario libera el mutex, otro hilo puede adquirir el mutex a su vez.
Addendum (julio 2016)
La sincronización se produce en un objeto . Esto significa que llamar a un método sincronizado de una clase bloqueará this
objeto de la llamada. Los métodos sincronizados estáticos bloquearán el objeto Class
sí.
Del mismo modo, ingresar un bloque sincronizado requiere bloquear this
objeto del método.
Esto significa que un método (o bloque) sincronizado puede ejecutarse en varios subprocesos al mismo tiempo si se están bloqueando en diferentes objetos, pero solo un hilo puede ejecutar un método (o bloque) sincronizado a la vez para cualquier objeto individual dado.
El modificador volátil de Java es un ejemplo de un mecanismo especial para garantizar que la comunicación ocurra entre subprocesos. Cuando un hilo escribe en una variable volátil y otro hilo ve esa escritura, el primer hilo le dice al segundo sobre todos los contenidos 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 multihilo para evitar inconsistencias en los datos.
Una sincronización volátil + es una solución infalible para que una operación (declaración) sea completamente atómica e incluye múltiples instrucciones para la CPU.
Decir, por ejemplo: volatile 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 realizar el cálculo incrementando el valor existente con uno (2 + 1 = 3 en el acumulador) y luego volver a escribir ese valor incrementado de vuelta a la memoria. Estas operaciones no son lo suficientemente atómicas aunque el valor es de i es volátil. ser volátil garantiza solo que una SOLA lectura / escritura de la memoria es atómica y no con MÚLTIPLE. Por lo tanto, necesitamos haber sincronizado también alrededor de i ++ para mantenerlo como una declaración atómica a prueba de tontos. Recuerde el hecho de que una declaración incluye varias declaraciones.
Espero que la explicación sea lo suficientemente clara.
Usted está preguntando específicamente sobre cómo trabajan internamente , así que aquí están:
Sin sincronizacion
private int counter;
public int getNextUniqueIndex() {
return counter++;
}
Básicamente lee el valor de la memoria, lo incrementa y vuelve a la memoria. Esto funciona en un solo hilo, pero en la actualidad, en la era de los cachés de múltiples núcleos, múltiples CPU y multinivel, no funcionará correctamente. En primer lugar, introduce la condición de carrera (varios hilos pueden leer el valor al mismo tiempo), pero también problemas de visibilidad. Es posible que el valor solo se almacene en la memoria de la CPU " local " (algo de caché) y no sea visible para otras CPU / núcleos (y, por lo tanto, - hilos). Es por esto que muchos se refieren a la copia local de una variable en un hilo. Es muy inseguro. Considera este popular pero roto código de detención de hilos:
private boolean stopped;
public void run() {
while(!stopped) {
//do some work
}
}
public void pleaseStop() {
stopped = true;
}
Agregue volatile
a la variable stopped
y funciona bien: si cualquier otro subproceso modifica la variable stopped
mediante el método pleaseStop()
, tiene la garantía de ver ese cambio inmediatamente en el bucle while(!stopped)
pleaseStop()
del subproceso activo. Por cierto, esta no es una buena forma de interrumpir un hilo, vea: Cómo detener un hilo que se está ejecutando para siempre sin ningún uso y Cómo detener un hilo Java específico .
AtomicInteger
private AtomicInteger counter = new AtomicInteger();
public int getNextUniqueIndex() {
return counter.getAndIncrement();
}
La clase AtomicInteger
usa operaciones de CPU de bajo nivel CAS ( compare-and-swap ) (¡no se necesita sincronización!) Le permiten modificar una variable en particular solo si el valor actual es igual a otra cosa (y se devuelve con éxito). Así que cuando ejecutas getAndIncrement()
en realidad se ejecuta en un bucle (implementación real simplificada):
int current;
do {
current = get();
} while(!compareAndSet(current, current + 1));
Así que básicamente: leer; tratar de almacenar el valor incrementado; si no tiene éxito (el valor ya no es igual al current
), lea e intente nuevamente. El compareAndSet()
se implementa en código nativo (ensamblado).
volatile
sin sincronización
private volatile int counter;
public int getNextUniqueIndex() {
return counter++;
}
Este código no es correcto. Corrige el problema de visibilidad ( volatile
se asegura de que otros subprocesos puedan ver el cambio realizado en el counter
) pero aún así tiene una condición de carrera. Esto se ha explained varias veces: pre / post-incremento no es atómico.
El único efecto secundario de la volatile
es el " vaciado " de las 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; Es por eso que volatile
no es por defecto.
volatile
sin sincronización (2)
volatile int i = 0;
void incIBy5() {
i += 5;
}
El mismo problema que el anterior, pero peor aún porque no es private
. La condición de carrera todavía está presente. Por qué es un problema? Si, digamos, dos hilos ejecutan este código simultáneamente, la salida podría ser + 5
o + 10
. Sin embargo, está garantizado para ver el cambio.
Múltiples independientes synchronized
void incIBy5() {
int temp;
synchronized(i) { temp = i }
synchronized(i) { i = temp + 5 }
}
Sorpresa, este código es incorrecto también. De hecho, está completamente equivocado. En primer lugar, está sincronizando en i
, que está a punto de cambiarse (además, i
es una primitiva, así que supongo que está sincronizando en un Integer
temporal creado a través de autoboxing ...) Completamente defectuoso. También podrías escribir:
synchronized(new Object()) {
//thread-safe, SRSLy?
}
No hay dos hilos que puedan entrar en el mismo bloque synchronized
con el mismo bloqueo . En este caso (y de manera similar en su código) el objeto de bloqueo cambia en cada ejecución, por lo que la synchronized
efectivamente no tiene efecto.
Incluso si ha utilizado una variable final (o this
) para la sincronización, el código sigue siendo incorrecto. Dos hilos primero pueden leer i
a temp
sincrónicamente (tener el mismo valor localmente en temp
), luego el primero asigna un nuevo valor a i
(por ejemplo, de 1 a 6) y el otro hace lo mismo (de 1 a 6) .
La sincronización debe abarcar desde la lectura hasta la asignación de un valor. Su primera sincronización no tiene efecto (leer un int
es atómico) y la segunda también. En mi opinión, estas son las formas correctas:
void synchronized incIBy5() {
i += 5
}
void incIBy5() {
synchronized(this) {
i += 5
}
}
void incIBy5() {
synchronized(this) {
int temp = i;
i = temp + 5;
}
}
volátil:
volatile
es una palabra clave. volatile
obliga a todos los subprocesos a obtener el último valor de la variable de la memoria principal en lugar del caché. No se requiere bloqueo para acceder a las variables volátiles. Todos los hilos pueden acceder al valor variable volátil al mismo tiempo.
El uso de variables volatile
reduce el riesgo de errores de consistencia de la memoria, porque cualquier escritura en una variable volátil establece una relación de suceso antes con lecturas posteriores de esa misma variable.
Esto significa que los cambios en una variable volatile
siempre son visibles para otros subprocesos . Además, también significa que cuando un hilo lee una variable volatile
, no solo ve el último cambio en la volatilidad, sino también los efectos secundarios del código que provocó el cambio .
Cuándo usar: Un hilo modifica los datos y otros hilos tienen que leer el último valor de los datos. Otros subprocesos tomarán alguna acción pero no actualizarán los datos .
AtomicXXX:
AtomicXXX
clases AtomicXXX
admiten la programación segura de subprocesos sin bloqueo en variables individuales. Estas clases AtomicXXX
(como AtomicInteger
) resuelven errores de inconsistencia de memoria / efectos secundarios de la modificación de variables volátiles, a las que se ha accedido en varios subprocesos.
Cuándo usar: varios 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 el método como sincronizado tiene dos efectos:
En primer lugar, no es posible intercalar dos invocaciones de métodos
synchronized
en el mismo objeto. Cuando un subproceso está ejecutando un métodosynchronized
para un objeto, todos los demás subprocesos que invocan métodossynchronized
para el mismo bloque de objeto (suspensión de ejecución) hasta que el primer subproceso se realiza con el objeto.Segundo, cuando un método
synchronized
sale, automáticamente establece una relación de suceso antes de cualquier invocación posterior de un métodosynchronized
para el mismo objeto. Esto garantiza que los cambios en el estado del objeto sean visibles para todos los subprocesos.
Cuándo usar: varios hilos pueden leer y modificar datos. Su lógica de negocios no solo actualiza los datos sino que también ejecuta operaciones atómicas
AtomicXXX
es equivalente a volatile + synchronized
aunque la implementación sea diferente. AmtomicXXX
extiende volatile
variables volatile
+ los métodos compareAndSet
pero no usa la sincronización.
Preguntas de SE relacionadas:
Diferencia entre lo volátil y lo sincronizado en Java.
Volatile boolean vs atomic boolean
Buenos artículos para leer: (El contenido anterior está tomado 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
Sincronizado Vs Atómico Vs Volátil:
1. Volatile y Atomic se aplican solo en la variable, mientras que Synchronized se aplica en el método.
2. Volatile garantiza la visibilidad y no la atomicidad / consistencia del objeto, mientras que otros aseguran la visibilidad y la atomicidad.
3. Almacenamiento variable volátil en la memoria RAM y su acceso es más rápido, pero no podemos lograr la seguridad o la sincronización de subprocesos sin una palabra clave sincronizada.
4. Sincronizado implementado como bloque sincronizado o método sincronizado mientras que ambos no. Podemos enlazar varias líneas de código seguras con la ayuda de una palabra clave sincronizada, mientras que con ambas no podemos lograr lo mismo.
5. Synchronized puede bloquear el mismo objeto de clase o un objeto de clase diferente, mientras que ambos no pueden.
Por favor, corrígeme si algo me lo perdí.