Quando usiamo AtomicReference
?
È necessario creare oggetti in tutti i programmi multithread?
Fornisci un semplice esempio di utilizzo di AtomicReference.
Quando usiamo AtomicReference
?
È necessario creare oggetti in tutti i programmi multithread?
Fornisci un semplice esempio di utilizzo di AtomicReference.
Risposte:
Il riferimento atomico deve essere utilizzato in un'impostazione in cui è necessario eseguire semplici operazioni atomiche (ovvero thread-safe , non banali) su un riferimento, per le quali la sincronizzazione basata su monitor non è appropriata. Supponiamo di voler verificare se un campo specifico solo se lo stato dell'oggetto rimane l'ultimo controllo:
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);
A causa della semantica di riferimento atomico, puoi farlo anche se l' cache
oggetto è condiviso tra i thread, senza usare synchronized
. In generale, è meglio usare i sincronizzatori o il java.util.concurrent
framework piuttosto che nudi a Atomic*
meno che non si sappia cosa si sta facendo.
Due eccellenti riferimenti ad albero morto che ti introdurranno a questo argomento:
Si noti che (non so se sia sempre stato vero) l' assegnazione di riferimento (ovvero =
) è essa stessa atomica (l'aggiornamento di tipi primitivi a 64 bit come long
o double
potrebbe non essere atomico; ma l'aggiornamento di un riferimento è sempre atomico, anche se è a 64 bit ) senza usare esplicitamente un Atomic*
.
Vedere la specifica del linguaggio Java 3ed, sezione 17.7 .
AtomicReference
è necessario contrassegnare la variabile volatile
perché, mentre il runtime garantisce che l'assegnazione di riferimento è atomica, il compilatore può eseguire ottimizzazioni supponendo che la variabile non sia stata modificata da altri thread.
AtomicReference
"; se lo stai usando, il mio consiglio sarebbe di andare nella direzione opposta e contrassegnarlo in final
modo che il compilatore possa ottimizzare di conseguenza.
Un riferimento atomico è ideale da utilizzare quando è necessario condividere e modificare lo stato di un oggetto immutabile tra più thread. Questa è un'affermazione super densa, quindi la analizzerò un po '.
Innanzitutto, un oggetto immutabile è un oggetto che non viene effettivamente modificato dopo la costruzione. Spesso i metodi di un oggetto immutabile restituiscono nuove istanze di quella stessa classe. Alcuni esempi includono le classi wrapper di Long e Double, oltre a String, solo per citarne alcuni. (Secondo la programmazione della concorrenza sugli oggetti immutabili JVM sono una parte critica della concorrenza moderna).
Successivamente, perché AtomicReference è meglio di un oggetto volatile per condividere quel valore condiviso. Un semplice esempio di codice mostrerà la differenza.
volatile String sharedValue;
static final Object lock=new Object();
void modifyString(){
synchronized(lock){
sharedValue=sharedValue+"something to add";
}
}
Ogni volta che vuoi modificare la stringa a cui fa riferimento quel campo volatile in base al suo valore corrente, devi prima ottenere un blocco su quell'oggetto. Questo impedisce ad altri thread di entrare nel frattempo e modificare il valore nel mezzo della nuova concatenazione di stringhe. Quindi quando il tuo thread riprende, intaserai il lavoro dell'altro thread. Ma onestamente quel codice funzionerà, sembra pulito e renderebbe felici molte persone.
Leggero problema. È lento. Soprattutto se c'è molta contesa di quell'oggetto Lock. Questo perché la maggior parte dei blocchi richiede una chiamata di sistema del sistema operativo e il thread si bloccherà e verrà contestualizzato dalla CPU per far posto ad altri processi.
L'altra opzione è utilizzare un AtomicRefrence.
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);
}
Ora, perché è meglio? Sinceramente quel codice è un po 'meno pulito di prima. Ma c'è qualcosa di veramente importante che accade sotto il cofano in AtomicRefrence, ed è il confronto e lo scambio. È una singola istruzione della CPU, non una chiamata del sistema operativo, che consente lo switch. Questa è una singola istruzione sulla CPU. E poiché non ci sono blocchi, non esiste alcun interruttore di contesto nel caso in cui il blocco venga esercitato, il che consente di risparmiare ancora più tempo!
Il problema è, per AtomicReferences, che non utilizza una chiamata .equals (), ma invece un confronto == per il valore atteso. Quindi assicurati che l'atteso sia l'oggetto reale restituito da get in the loop.
worked
per ottenere la stessa semantica.
Ecco un caso d'uso per AtomicReference:
Considera questa classe che funge da intervallo di numeri e utilizza le singole variabili AtmomicInteger per mantenere i limiti di numero inferiore e superiore.
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());
}
}
Sia setLower che setUpper sono sequenze check-then-act, ma non usano un blocco sufficiente per renderle atomiche. Se l'intervallo di numeri è valido (0, 10) e un thread chiama setLower (5) mentre un altro thread chiama setUpper (4), con un certo sfortunato tempo entrambi passeranno i controlli nei setter e verranno applicate entrambe le modifiche. Il risultato è che l'intervallo ora mantiene (5, 4) uno stato non valido. Quindi, mentre gli AtomicIntegers sottostanti sono thread-safe, la classe composita non lo è. Questo può essere risolto utilizzando AtomicReference invece di utilizzare singoli AtomicInteger per i limiti superiore e inferiore.
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;
}
}
}
È possibile utilizzare AtomicReference quando si applicano blocchi ottimistici. Hai un oggetto condiviso e vuoi cambiarlo da più di 1 thread.
Poiché altri thread potrebbero averlo modificato e / possono modificare tra questi 2 passaggi. Devi farlo in un'operazione atomica. questo è dove AtomicReference può aiutare
Ecco un caso d'uso molto semplice e non ha nulla a che fare con la sicurezza del thread.
Per condividere un oggetto tra invocazioni lambda, AtomicReference
è un'opzione :
public void doSomethingUsingLambdas() {
AtomicReference<YourObject> yourObjectRef = new AtomicReference<>();
soSomethingThatTakesALambda(() -> {
yourObjectRef.set(youObject);
});
soSomethingElseThatTakesALambda(() -> {
YourObject yourObject = yourObjectRef.get();
});
}
Non sto dicendo che questo sia un buon design o altro (è solo un esempio banale), ma se hai il caso in cui devi condividere un oggetto tra invocazioni lambda, AtomicReference
è un'opzione.
In effetti puoi usare qualsiasi oggetto che contiene un riferimento, anche una Collezione che ha un solo oggetto. Tuttavia, AtomicReference si adatta perfettamente.
Non parlerò molto. Già i miei amici rispettati hanno dato il loro prezioso contributo. Il codice in esecuzione completo all'ultimo di questo blog dovrebbe eliminare qualsiasi confusione. Si tratta di un posto al cinema che prenota un piccolo programma in uno scenario multi-thread.
Alcuni fatti elementari importanti sono i seguenti. 1> Thread diversi possono contendere solo per esempio e variabili membro statiche nello spazio heap. 2> Lettura o scrittura volatile sono completamente atomiche e serializzate / avvengono prima e vengono eseguite solo dalla memoria. Dicendo questo intendo che qualsiasi lettura seguirà la precedente scrittura in memoria. E qualsiasi scrittura seguirà la lettura precedente dalla memoria. Quindi ogni thread che lavora con un volatile vedrà sempre il valore più aggiornato. AtomicReference utilizza questa proprietà di volatile.
Di seguito sono riportati alcuni dei codici sorgente di AtomicReference. AtomicReference si riferisce a un riferimento a un oggetto. Questo riferimento è una variabile membro volatile nell'istanza AtomicReference come di seguito.
private volatile V value;
get () restituisce semplicemente l'ultimo valore della variabile (come fanno i volatili in un modo "succede prima").
public final V get()
Di seguito è riportato il metodo più importante di AtomicReference.
public final boolean compareAndSet(V expect, V update) {
return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
}
Il metodo compareAndSet (prevede, aggiorna) chiama il metodo compareAndSwapObject () della classe non sicura di Java. Questa chiamata di metodo non sicura richiama la chiamata nativa, che richiama una singola istruzione al processore. "prevedono" e "aggiorna" ogni riferimento a un oggetto.
Se e solo se la variabile "value" del membro dell'istanza AtomicReference fa riferimento allo stesso oggetto viene definita da "prevedono", "update" viene assegnato a questa variabile di istanza ora e viene restituito "true". Altrimenti, viene restituito falso. Il tutto è fatto atomicamente. Nessun altro thread può intercettare in mezzo. Poiché si tratta di un'operazione a singolo processore (magia della moderna architettura informatica), spesso è più veloce dell'uso di un blocco sincronizzato. Ma ricorda che quando più variabili devono essere aggiornate atomicamente, AtomicReference non aiuta.
Vorrei aggiungere un codice di esecuzione completo, che può essere eseguito in eclissi. Cancellerebbe molta confusione. Qui 22 utenti (thread MyTh) stanno provando a prenotare 20 posti. Di seguito è riportato lo snippet di codice seguito dal codice completo.
Snippet di codice in cui 22 utenti stanno cercando di prenotare 20 posti.
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();
}
Di seguito è riportato il codice completo in esecuzione.
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
}
}
}
}
Quando utilizziamo AtomicReference?
AtomicReference è un modo flessibile per aggiornare atomicamente il valore della variabile senza usare la sincronizzazione.
AtomicReference
supporta la programmazione thread-safe senza blocco su singole variabili.
Esistono diversi modi per ottenere la sicurezza dei thread con API simultanee di alto livello . Le variabili atomiche sono una delle molteplici opzioni.
Lock
gli oggetti supportano espressioni di blocco che semplificano molte applicazioni simultanee.
Executors
definire un'API di alto livello per l'avvio e la gestione dei thread. Le implementazioni di Executor fornite da java.util.concurrent forniscono la gestione del pool di thread adatta per applicazioni su larga scala.
Le raccolte simultanee semplificano la gestione di grandi raccolte di dati e possono ridurre notevolmente la necessità di sincronizzazione.
Le variabili atomiche hanno funzionalità che riducono al minimo la sincronizzazione e aiutano a evitare errori di coerenza della memoria.
Fornisci un semplice esempio di utilizzo di AtomicReference.
Codice di esempio 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);
È necessario creare oggetti in tutti i programmi multithread?
Non è necessario utilizzare AtomicReference
in tutti i programmi multi thread.
Se si desidera proteggere una singola variabile, utilizzare AtomicReference
. Se vuoi proteggere un blocco di codice, usa altri costrutti come Lock
/ synchronized
ecc.
Un altro semplice esempio è quello di effettuare una modifica thread sicura in un oggetto sessione.
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;
}
}
Fonte: http://www.ibm.com/developerworks/library/j-jtp09238/index.html