¿Cuál es la diferencia entre atómico / volátil / sincronizado?


¿Cómo funciona internamente el trabajo 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++; 
}

¿Funciona volatile de la siguiente manera? Is

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 que thread-safe?

¿Y cuál es la diferencia entre la lectura interna y la escritura de 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?

Author: PhoenixPan, 2012-03-17

6 answers

Usted está preguntando específicamente acerca de cómo ellos internamente funcionan , así que aquí está: {[44]]}

Sin sincronización

private int counter;

public int getNextUniqueIndex() {
  return counter++; 
}

Básicamente lee el valor de la memoria, lo incrementa y lo devuelve a la memoria. Esto funciona en un solo hilo, pero hoy en día, en la era de multi-core, multi-CPU, cachés de varios niveles 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. El valor solo podría ser almacenado en" local " memoria de CPU (alguna caché) y no ser visible para otras CPU/núcleos (y por lo tanto - hilos). Esta es la razón por la que muchos se refieren a copia local de una variable en un hilo. Es muy inseguro. Considere este popular pero roto código de detención de hilo:

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 hilo modifica la variable stopped a través del método pleaseStop(), se garantiza que verá ese cambio inmediatamente en el bucle while(!stopped) del hilo de trabajo. Por cierto, esto es tampoco es una buena manera de interrumpir un subproceso, vea: Cómo detener un subproceso que se está ejecutando para siempre sin ningún uso y Detener un subproceso java específico.

AtomicInteger

private AtomicInteger counter = new AtomicInteger();

public int getNextUniqueIndex() {
  return counter.getAndIncrement();
}

La clase AtomicInteger utiliza 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 se ejecuta getAndIncrement() en realidad se ejecuta in a loop (simplified real implementation):

int current;
do {
  current = get();
} while(!compareAndSet(current, current + 1));

Así que básicamente: leer; tratar de almacenar valor incrementado; si no tiene éxito (el valor ya no es igual a current), leer e intentarlo de nuevo. 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. Soluciona el problema de visibilidad (volatile se asegura de que otros hilos puedan ver el cambio realizado en counter) pero todavía tiene una condición de carrera. Esto ha sido explicado varias veces: pre/post-incremento no es atómico.

El único efecto secundario de volatile es "flushing" 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 predeterminado.

volatile sin sincronización (2)

volatile int i = 0;
void incIBy5() {
  i += 5;
}

El mismo problema que el anterior, pero aún peor porque i no es private. La condición de carrera todavía está presente. ¿Por qué es un problema? Si, digamos, dos subprocesos ejecutan este código simultáneamente, la salida podría ser + 5 o + 10. Sin embargo, usted 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 también es incorrecto. De hecho, está completamente equivocado. En primer lugar se está sincronizando en i, que está a punto de ser cambiado (por otra parte, i es un primitivo, así que supongo que se está sincronizando en un Integer temporal creado a través de autoboxing... Completamente errónea. También podrías escribe:

synchronized(new Object()) {
  //thread-safe, SRSLy?
}

No hay dos hilos que puedan ingresar el mismo synchronized bloque 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 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 pueden leer primero i a temp sincrónicamente (teniendo el mismo valor localmente en temp), luego el primero asigna un nuevo valor a i (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 (leer un int es atómica) 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;
  }
}
 317
Author: Tomasz Nurkiewicz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:10:47

Declarar una variable como volatile significa que modificar su valor afecta inmediatamente al almacenamiento de memoria real para la variable. El compilador no puede optimizar cualquier referencia hecha a la variable. Esto garantiza que cuando un subproceso modifica la variable, todos los demás subprocesos ven el nuevo valor inmediatamente. (Esto no está garantizado para variables no volátiles.)

Declarar una variable atómica garantiza que las operaciones realizadas en la variable se producen en una la moda, es decir, que todos los subpasos de la operación se han completado en el hilo que se ejecutan y no son 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 ambos pasos se completarán como si fueran una sola operación indivisible/ininterrumpible.

Sincronizar todos los accesos a una variable permite solo un único subproceso en un momento para acceder a la variable, y obliga a todos los demás hilos a esperar a que el hilo de acceso para liberar 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 se sincronicen (por ejemplo, sincronizar todas las escrituras en una variable pero ninguna de las lecturas de se).

La atomicidad, la sincronización y la volatilidad son atributos independientes, pero generalmente se usan en combinación para imponer una cooperación de subprocesos adecuada para acceder a las variables.

Adición (Abril 2016)

El acceso sincronizado a una variable generalmente se implementa usando un monitor o un semáforo . Estos son mecanismos de bajo nivel mutex (exclusión mutua) que permiten que un hilo adquiera el control de una variable o bloque de código exclusivamente, forzando a todos los otros hilos 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.

Adición (Julio 2016)

La sincronización ocurre en un objeto . Esto significa que llamar a un método sincronizado de una clase bloqueará el objeto this de la llamada. Los métodos sincronizados estáticos bloquearán el objeto Class en sí.

Del mismo modo, al introducir un el bloque sincronizado requiere bloquear el objeto this del método.

Esto significa que un método sincronizado (o bloque) puede ejecutarse en múltiples subprocesos al mismo tiempo si están bloqueando diferentes objetos, pero solo un subproceso puede ejecutar un método sincronizado (o bloque) a la vez para cualquier único objeto.

 41
Author: David R Tribble,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-07-26 19:14:22

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 de la caché. No se requiere bloqueo para acceder a variables volátiles. Todos los hilos pueden acceder al valor de la variable volátil al mismo tiempo.

El uso de variables volatile reduce el riesgo de errores de consistencia de memoria, porque cualquier escritura en una variable volátil establece una relación sucede-antes con lecturas posteriores de esa misma variable.

Esto significa que los cambios a una variable volatile son siempre visibles para otros hilos. Lo que es más, también significa que cuando un hilo lee una variable volatile, no solo ve el último cambio en el volátil, sino también los efectos secundarios del código que condujo al cambio.

Cuándo usar: Un subproceso modifica los datos y otros subprocesos tienen que leer el último valor de los datos. Otros hilos tomarán alguna acción, pero no se actualizarán data .

AtomicXXX:

AtomicXXX las clases admiten programación segura de hilos 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 múltiples subprocesos.

Cuándo usar: Múltiples hilos pueden leer y modificar datos.

sincronizado:

synchronized es palabra clave utilizada para proteger un método o bloque de código. Al hacer el método como sincronizado tiene dos efectos:

  1. En primer lugar, no es posible intercalar dos invocaciones de métodos synchronized en el mismo objeto. Cuando un subproceso está ejecutando un método synchronized para un objeto, todos los demás subprocesos que invocan métodos synchronized para el mismo bloque de objeto (suspenden la ejecución) hasta que el primer subproceso termine con el objeto.

  2. En segundo lugar, cuando un método synchronized sale, establece automáticamente un sucede-antes de la relación con cualquier invocación posterior de un método synchronized para el mismo objeto. Esto garantiza que los cambios en el estado del objeto sean visibles para todos los subprocesos.

Cuándo usar: Múltiples hilos pueden leer y modificar datos. Su lógica de negocio no solo actualiza los datos, sino que también ejecuta operaciones atómicas

AtomicXXX es equivalente a volatile + synchronized aunque la implementación es diferente. AmtomicXXX extiende volatile variables + compareAndSet métodos pero no usa sincronización.

Preguntas relacionadas:

Diferencia entre volátil y sincronizado en Java

Volatile boolean vs AtomicBoolean

Buenos artículos para leer: (El contenido anterior está tomado de esta documentación pages)

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

 9
Author: Ravindra babu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:47:29

Sé que dos hilos no pueden entrar en Sincronizar bloque al mismo tiempo

Dos subprocesos 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 llevar a un código como este.

private Integer i = 0;

synchronized(i) {
   i++;
}

Esto no se comportará como se espera, ya que podría estar bloqueando un objeto diferente cada vez.

Si esto es cierto que Cómo este atómico.incrementAndGet() funciona sin Sincronizar ?? y es thread safe ??

Sí. 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.

¿Y cuál es la diferencia entre la lectura interna y la escritura a Variable Volátil / Variable Atómica ??

La clase atómica utiliza campos volátiles . No hay diferencia en el campo. La diferencia son las operaciones realizadas. Las clases atómicas utilizan 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 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 usar técnicas de seguridad de subprocesos.

Esto es solo un problema cuando se comparte memoria al menos un hilo lo actualiza.

 5
Author: Peter Lawrey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-03-17 12:13:01

Una sincronización volátil + es una solución infalible para que una operación(instrucción) sea completamente atómica que incluye múltiples instrucciones para la CPU.

Digamos 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 hacer con el cálculo incrementando el valor existente con uno (2 + 1 = 3 en el acumulador) y luego escribir de nuevo ese valor incrementado de nuevo a la memoria. Estas operaciones no son lo suficientemente atómicas aunque el valor es de i es volátil. i ser volátil garantiza solo que una SOLA lectura / escritura de la memoria es atómica y no con MÚLTIPLES. Por lo tanto, necesitamos tener sincronizado también alrededor de i++ para mantenerlo a prueba de tontos declaración atómica. Recuerde el hecho de que una declaración incluye varias declaraciones.

Espero que la explicación sea lo suficientemente clara.

 1
Author: Thomas Mathew,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-08-26 13:12:11

El modificador Java volatile es un ejemplo de un mecanismo especial para garantizar que la comunicación ocurra entre hilos. Cuando un hilo escribe a una variable volátil, y otro hilo ve que escribir, el primer hilo está diciendo el segundo sobre todos los contenidos de la memoria hasta que realizó la escritura a 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 necesidad en un entorno multihilo para evitar la inconsistencia de datos.

 0
Author: Sai prateek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-07-04 06:16:47