rootpane - textarea java netbeans




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

¿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?


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.


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.


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;
  }
}

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





volatile