¿Cuándo usamos AtomicReference
?
¿Es necesario crear objetos en todos los programas multiproceso?
Proporcione un ejemplo simple donde se debe usar AtomicReference.
¿Cuándo usamos AtomicReference
?
¿Es necesario crear objetos en todos los programas multiproceso?
Proporcione un ejemplo simple donde se debe usar AtomicReference.
Respuestas:
La referencia atómica debe usarse en un entorno en el que necesite realizar operaciones atómicas simples (es decir , seguras para subprocesos , no triviales) en una referencia, para la cual la sincronización basada en monitor no es adecuada. Suponga que desea verificar si un campo específico solo si el estado del objeto permanece como lo comprobó por última vez:
AtomicReference<Object> cache = new AtomicReference<Object>();
Object cachedValue = new Object();
cache.set(cachedValue);
//... time passes ...
Object cachedValueToUpdate = cache.get();
//... do some work to transform cachedValueToUpdate into a new version
Object newValue = someFunctionOfOld(cachedValueToUpdate);
boolean success = cache.compareAndSet(cachedValue,cachedValueToUpdate);
Debido a la semántica de referencia atómica, puede hacerlo incluso si el cache
objeto se comparte entre subprocesos, sin usar synchronized
. En general, es mejor usar sincronizadores o el java.util.concurrent
marco en lugar de estar desnudo a Atomic*
menos que sepa lo que está haciendo.
Dos excelentes referencias de árbol muerto que le presentarán este tema:
Tenga en cuenta que (no sé si esto siempre ha sido cierto) la asignación de referencia (es decir =
) es atómica en sí misma (actualizar tipos primitivos de 64 bits como long
o double
no puede ser atómica; pero actualizar una referencia siempre es atómica, incluso si es de 64 bits ) sin usar explícitamente un Atomic*
.
Consulte la Especificación del lenguaje Java 3ed, Sección 17.7 .
AtomicReference
, debe marcar la variable volatile
porque, si bien el tiempo de ejecución garantiza que la asignación de referencia es atómica, el compilador puede realizar optimizaciones bajo el supuesto de que la variable no estaba siendo modificada por otros subprocesos.
AtomicReference
"; si lo está utilizando, mi consejo sería ir en la dirección opuesta y marcarlo final
para que el compilador pueda optimizarlo en consecuencia.
Una referencia atómica es ideal para usar cuando necesita compartir y cambiar el estado de un objeto inmutable entre múltiples hilos. Esa es una declaración súper densa, así que la desglosaré un poco.
Primero, un objeto inmutable es un objeto que efectivamente no cambia después de la construcción. Con frecuencia, los métodos de un objeto inmutable devuelven nuevas instancias de esa misma clase. Algunos ejemplos incluyen las clases de contenedor de Long y Double, así como String, solo por nombrar algunos. (De acuerdo con la concurrencia de programación en la JVM, los objetos inmutables son una parte crítica de la concurrencia moderna).
A continuación, por qué AtomicReference es mejor que un objeto volátil para compartir ese valor compartido. Un ejemplo de código simple mostrará la diferencia.
volatile String sharedValue;
static final Object lock=new Object();
void modifyString(){
synchronized(lock){
sharedValue=sharedValue+"something to add";
}
}
Cada vez que desee modificar la cadena a la que hace referencia ese campo volátil en función de su valor actual, primero debe obtener un bloqueo en ese objeto. Esto evita que algún otro hilo entre mientras tanto y cambie el valor en el medio de la nueva concatenación de cadenas. Luego, cuando se reanuda su hilo, golpea el trabajo del otro hilo. Pero, sinceramente, ese código funcionará, se ve limpio y haría feliz a la mayoría de las personas.
Pequeño problema. Es lento. Especialmente si hay mucha contención de ese Objeto de bloqueo. Esto se debe a que la mayoría de los bloqueos requieren una llamada al sistema operativo, y su subproceso se bloqueará y se cambiará el contexto de la CPU para dar paso a otros procesos.
La otra opción es usar una referencia atómica.
public static AtomicReference<String> shared = new AtomicReference<>();
String init="Inital Value";
shared.set(init);
//now we will modify that value
boolean success=false;
while(!success){
String prevValue=shared.get();
// do all the work you need to
String newValue=shared.get()+"lets add something";
// Compare and set
success=shared.compareAndSet(prevValue,newValue);
}
Ahora, ¿por qué es esto mejor? Honestamente, ese código es un poco menos limpio que antes. Pero hay algo realmente importante que sucede debajo del capó en AtomicRefrence, y es comparar e intercambiar. Es una sola instrucción de CPU, no una llamada del sistema operativo, lo que hace que el cambio ocurra. Esa es una sola instrucción en la CPU. Y debido a que no hay bloqueos, no hay cambio de contexto en el caso de que el bloqueo se ejerza, lo que ahorra aún más tiempo.
El problema es que, para AtomicReferences, esto no utiliza una llamada .equals (), sino una comparación == para el valor esperado. Así que asegúrese de que lo esperado sea el objeto real devuelto por get in the loop.
worked
que seguir para obtener la misma semántica.
Aquí hay un caso de uso para AtomicReference:
Considere esta clase que actúa como un rango de números y utiliza variables AtmomicInteger individuales para mantener los límites numéricos inferior y superior.
public class NumberRange {
// INVARIANT: lower <= upper
private final AtomicInteger lower = new AtomicInteger(0);
private final AtomicInteger upper = new AtomicInteger(0);
public void setLower(int i) {
// Warning -- unsafe check-then-act
if (i > upper.get())
throw new IllegalArgumentException(
"can't set lower to " + i + " > upper");
lower.set(i);
}
public void setUpper(int i) {
// Warning -- unsafe check-then-act
if (i < lower.get())
throw new IllegalArgumentException(
"can't set upper to " + i + " < lower");
upper.set(i);
}
public boolean isInRange(int i) {
return (i >= lower.get() && i <= upper.get());
}
}
Tanto setLower como setUpper son secuencias de verificar y luego actuar, pero no usan suficiente bloqueo para hacerlas atómicas. Si el rango de números se mantiene (0, 10), y un subproceso llama a setLower (5) mientras que otro subproceso llama a setUpper (4), con un tiempo desafortunado, ambos pasarán las verificaciones en los setters y se aplicarán ambas modificaciones. El resultado es que el rango ahora contiene (5, 4) un estado no válido. Entonces, mientras que los AtomicIntegers subyacentes son seguros para subprocesos, la clase compuesta no lo es. Esto se puede solucionar mediante el uso de AtomicReference en lugar de utilizar AtomicIntegers individuales para los límites superior e inferior.
public class CasNumberRange {
// Immutable
private static class IntPair {
final int lower; // Invariant: lower <= upper
final int upper;
private IntPair(int lower, int upper) {
this.lower = lower;
this.upper = upper;
}
}
private final AtomicReference<IntPair> values =
new AtomicReference<IntPair>(new IntPair(0, 0));
public int getLower() {
return values.get().lower;
}
public void setLower(int lower) {
while (true) {
IntPair oldv = values.get();
if (lower > oldv.upper)
throw new IllegalArgumentException(
"Can't set lower to " + lower + " > upper");
IntPair newv = new IntPair(lower, oldv.upper);
if (values.compareAndSet(oldv, newv))
return;
}
}
public int getUpper() {
return values.get().upper;
}
public void setUpper(int upper) {
while (true) {
IntPair oldv = values.get();
if (upper < oldv.lower)
throw new IllegalArgumentException(
"Can't set upper to " + upper + " < lower");
IntPair newv = new IntPair(oldv.lower, upper);
if (values.compareAndSet(oldv, newv))
return;
}
}
}
Puede usar AtomicReference al aplicar bloqueos optimistas. Tiene un objeto compartido y desea cambiarlo desde más de 1 hilo.
Como otro hilo podría haberlo modificado y / o puede modificar entre estos 2 pasos. Necesitas hacerlo en una operación atómica. aquí es donde AtomicReference puede ayudar
Aquí hay un caso de uso muy simple y no tiene nada que ver con la seguridad del hilo.
Para compartir un objeto entre invocaciones lambda, AtomicReference
es una opción :
public void doSomethingUsingLambdas() {
AtomicReference<YourObject> yourObjectRef = new AtomicReference<>();
soSomethingThatTakesALambda(() -> {
yourObjectRef.set(youObject);
});
soSomethingElseThatTakesALambda(() -> {
YourObject yourObject = yourObjectRef.get();
});
}
No estoy diciendo que este sea un buen diseño ni nada (es solo un ejemplo trivial), pero si tiene el caso en el que necesita compartir un objeto entre invocaciones lambda, AtomicReference
es una opción.
De hecho, puede usar cualquier objeto que contenga una referencia, incluso una Colección que solo tenga un elemento. Sin embargo, la referencia atómica es perfecta.
No hablaré mucho Ya mis respetados compañeros amigos han dado su valiosa aportación. El código de ejecución completo al final de este blog debería eliminar cualquier confusión. Se trata de un pequeño programa de reserva de asientos de cine en un escenario de subprocesos múltiples.
Algunos hechos elementales importantes son los siguientes. 1> Diferentes subprocesos solo pueden competir, por ejemplo, con variables miembro estáticas en el espacio de almacenamiento dinámico. 2> La lectura o escritura volátil es completamente atómica y serializada / ocurre antes y solo se realiza desde la memoria. Al decir esto quiero decir que cualquier lectura seguirá a la escritura anterior en la memoria. Y cualquier escritura seguirá la lectura anterior de la memoria. Por lo tanto, cualquier hilo que trabaje con un volátil siempre verá el valor más actualizado. AtomicReference utiliza esta propiedad de volátil.
Los siguientes son algunos de los códigos fuente de AtomicReference. AtomicReference se refiere a una referencia de objeto. Esta referencia es una variable miembro volátil en la instancia de AtomicReference como se muestra a continuación.
private volatile V value;
get () simplemente devuelve el último valor de la variable (como lo hacen los volátiles de una manera "ocurre antes").
public final V get()
El siguiente es el método más importante de referencia atómica.
public final boolean compareAndSet(V expect, V update) {
return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
}
El método compareAndSet (esperar, actualizar) llama al método compareAndSwapObject () de la clase insegura de Java. Este método de llamada de inseguro invoca la llamada nativa, que invoca una sola instrucción para el procesador. "esperar" y "actualizar" cada referencia a un objeto.
Si y solo si el "valor" de la variable de miembro de instancia AtomicReference se refiere al mismo objeto al que se hace referencia por "esperar", ahora se asigna "actualización" a esta variable de instancia y se devuelve "verdadero". O bien, se devuelve falso. Todo se hace atómicamente. Ningún otro hilo puede interceptar en el medio. Como se trata de una operación de procesador único (magia de la arquitectura moderna de la computadora), a menudo es más rápido que usar un bloque sincronizado. Pero recuerde que cuando varias variables necesitan actualizarse atómicamente, AtomicReference no ayudará.
Me gustaría agregar un código de ejecución completo, que se puede ejecutar en eclipse. Despejaría mucha confusión. Aquí 22 usuarios (hilos MyTh) están tratando de reservar 20 asientos. A continuación se muestra el fragmento de código seguido del código completo.
Fragmento de código donde 22 usuarios intentan reservar 20 asientos.
for (int i = 0; i < 20; i++) {// 20 seats
seats.add(new AtomicReference<Integer>());
}
Thread[] ths = new Thread[22];// 22 users
for (int i = 0; i < ths.length; i++) {
ths[i] = new MyTh(seats, i);
ths[i].start();
}
El siguiente es el código de ejecución completo.
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
public class Solution {
static List<AtomicReference<Integer>> seats;// Movie seats numbered as per
// list index
public static void main(String[] args) throws InterruptedException {
// TODO Auto-generated method stub
seats = new ArrayList<>();
for (int i = 0; i < 20; i++) {// 20 seats
seats.add(new AtomicReference<Integer>());
}
Thread[] ths = new Thread[22];// 22 users
for (int i = 0; i < ths.length; i++) {
ths[i] = new MyTh(seats, i);
ths[i].start();
}
for (Thread t : ths) {
t.join();
}
for (AtomicReference<Integer> seat : seats) {
System.out.print(" " + seat.get());
}
}
/**
* id is the id of the user
*
* @author sankbane
*
*/
static class MyTh extends Thread {// each thread is a user
static AtomicInteger full = new AtomicInteger(0);
List<AtomicReference<Integer>> l;//seats
int id;//id of the users
int seats;
public MyTh(List<AtomicReference<Integer>> list, int userId) {
l = list;
this.id = userId;
seats = list.size();
}
@Override
public void run() {
boolean reserved = false;
try {
while (!reserved && full.get() < seats) {
Thread.sleep(50);
int r = ThreadLocalRandom.current().nextInt(0, seats);// excludes
// seats
//
AtomicReference<Integer> el = l.get(r);
reserved = el.compareAndSet(null, id);// null means no user
// has reserved this
// seat
if (reserved)
full.getAndIncrement();
}
if (!reserved && full.get() == seats)
System.out.println("user " + id + " did not get a seat");
} catch (InterruptedException ie) {
// log it
}
}
}
}
¿Cuándo usamos la referencia atómica?
AtomicReference es una forma flexible de actualizar el valor de la variable atómicamente sin el uso de sincronización.
AtomicReference
admite programación segura y sin hilos de bloqueo en variables individuales.
Hay varias formas de lograr la seguridad de subprocesos con API concurrente de alto nivel . Las variables atómicas son una de las múltiples opciones.
Lock
Los objetos admiten modismos de bloqueo que simplifican muchas aplicaciones concurrentes.
Executors
definir una API de alto nivel para lanzar y administrar hilos. Las implementaciones de ejecutor proporcionadas por java.util.concurrent proporcionan una gestión de grupo de subprocesos adecuada para aplicaciones a gran escala.
Las colecciones simultáneas facilitan la gestión de grandes colecciones de datos y pueden reducir en gran medida la necesidad de sincronización.
Las variables atómicas tienen características que minimizan la sincronización y ayudan a evitar errores de consistencia de la memoria.
Proporcione un ejemplo simple donde se debe usar AtomicReference.
Código de muestra con AtomicReference
:
String initialReference = "value 1";
AtomicReference<String> someRef =
new AtomicReference<String>(initialReference);
String newReference = "value 2";
boolean exchanged = someRef.compareAndSet(initialReference, newReference);
System.out.println("exchanged: " + exchanged);
¿Es necesario crear objetos en todos los programas multiproceso?
No tiene que usarlo AtomicReference
en todos los programas de subprocesos múltiples.
Si desea proteger una sola variable, use AtomicReference
. Si desea proteger un bloque de código, use otras construcciones como Lock
/ synchronized
etc.
Otro ejemplo simple es hacer una modificación de hilo seguro en un objeto de sesión.
public PlayerScore getHighScore() {
ServletContext ctx = getServletConfig().getServletContext();
AtomicReference<PlayerScore> holder
= (AtomicReference<PlayerScore>) ctx.getAttribute("highScore");
return holder.get();
}
public void updateHighScore(PlayerScore newScore) {
ServletContext ctx = getServletConfig().getServletContext();
AtomicReference<PlayerScore> holder
= (AtomicReference<PlayerScore>) ctx.getAttribute("highScore");
while (true) {
HighScore old = holder.get();
if (old.score >= newScore.score)
break;
else if (holder.compareAndSet(old, newScore))
break;
}
}
Fuente: http://www.ibm.com/developerworks/library/j-jtp09238/index.html