Quando utilizzare AtomicReference in Java?

Quando usiamo AtomicReference?

È necessario creare oggetti in tutti i programmi multithread?

Fornire un semplice esempio in cui dovrebbe essere utilizzato AtomicReference.

Il riferimento atomico dovrebbe essere utilizzato in un’impostazione in cui è necessario eseguire operazioni atomiche semplici (ad esempio thread-safe , non banali) su un riferimento, per il quale la sincronizzazione basata sul monitor non è appropriata. Supponiamo di voler verificare se un campo specifico solo se lo stato dell’object rimane come l’ultima volta controllato:

AtomicReference cache = new AtomicReference(); 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, è ansible farlo anche se l’object cache è condiviso tra thread, senza utilizzare synchronized . In generale, è meglio usare i sincronizzatori o il framework java.util.concurrent piuttosto che Atomic* meno che non si sappia cosa si sta facendo.

Due eccellenti riferimenti ad albero morto che ti introdurranno a questo argomento:

  • L’eccellente arte di Herlihy per la programmazione multiprocessore
  • Concorrenza Java in pratica

Si noti che (non so se questo è sempre stato vero) l’assegnazione di riferimento (cioè = ) è essa stessa atomica (l’aggiornamento di tipi a 64 bit primitivi come long o double potrebbe non essere atomico, ma l’aggiornamento di un riferimento è sempre atomico, anche se è 64 bit) senza usare esplicitamente un Atomic* .
Vedere la specifica della lingua Java 3ed, Sezione 17.7 .

Un riferimento atomico è ideale da utilizzare quando è necessario condividere e modificare lo stato di un object immutabile tra più thread. Questa è un’affermazione super densa, quindi la spaccerò un po ‘.

Innanzitutto, un object immutabile è un object che non viene effettivamente modificato dopo la costruzione. Spesso i metodi di un object immutabile restituiscono nuove istanze di quella stessa class. Alcuni esempi includono le classi wrapper di Long e Double, oltre a String, solo per citarne alcuni. (Secondo Programming Concurrency sugli oggetti immutabili JVM sono una parte critica della concorrenza moderna).

Quindi, perché AtomicReference è migliore di un object 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 referenziata da quel campo volatile in base al suo valore corrente, devi prima ottenere un blocco su quell’object. Questo impedisce ad altri thread di entrare nel frattempo e di cambiare il valore nel mezzo della nuova concatenazione di stringhe. Quindi, quando il thread riprende, si blocca il lavoro dell’altro thread. Ma onestamente quel codice funzionerà, sembra pulito e renderebbe la maggior parte felice.

Leggero problema È lento Soprattutto se c’è un sacco di contesa su quell’object di blocco. Questo perché la maggior parte dei blocchi richiede una chiamata di sistema OS, e il thread si bloccherà e verrà distriggersto dal contesto della CPU per far posto ad altri processi.

L’altra opzione è usare un AtomicRefrence.

 public static AtomicReference 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? Onestamente quel codice è un po ‘meno pulito di prima. Ma c’è qualcosa di veramente importante che accade sotto la cappa di AtomicRefrence, e questo è confronto e scambio. È una singola istruzione della CPU, non una chiamata del SO, che fa sì che l’interruttore avvenga. Questa è una singola istruzione sulla CPU. E poiché non ci sono serrature, non esiste un interruttore di contesto nel caso in cui venga esercitata la serratura che consente di risparmiare ancora più tempo!

Il problema è che, per AtomicReferences, non viene utilizzata una chiamata .equals (), ma un confronto == per il valore previsto. Quindi assicurati che l’atteso sia l’object reale restituito da get in the loop.

Ecco un caso d’uso per AtomicReference:

Considerare questa class che funge da intervallo numerico 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 utilizzano un blocco sufficiente per renderli atomici. Se l'intervallo di numeri contiene (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 ed entrambe le modifiche verranno applicate. Il risultato è che l'intervallo ora contiene (5, 4) uno stato non valido. Quindi, mentre gli AtomicInteger sottostanti sono thread-safe, la class composita non lo è. Questo problema può essere risolto utilizzando AtomicReference anziché 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 final AtomicReference values = new AtomicReference(new IntPair(0, 0)); public int getLower() { return values.get().lower; } public int getUpper() { return values.get().upper; } public void setLower(int i) { while (true) { IntPair oldv = values.get(); if (i > oldv.upper) throw new IllegalArgumentException( "Can't set lower to " + i + " > upper"); IntPair newv = new IntPair(i, oldv.upper); if (values.compareAndSet(oldv, newv)) return; } } // similarly for setUpper } 

È ansible utilizzare AtomicReference quando si applicano blocchi ottimistici. Hai un object condiviso e vuoi cambiarlo da più di 1 thread.

  1. È ansible creare una copia dell’object condiviso
  2. Modifica l’object condiviso
  3. È necessario verificare che l’object condiviso sia sempre lo stesso di prima – in caso affermativo, aggiornare con il riferimento della copia modificata.

Come altro thread potrebbe averlo modificato e / può modificare tra questi 2 passaggi. Hai bisogno di farlo in un’operazione atomica. questo è dove AtomicReference può aiutare

Non parlerò molto. Già i miei amici più stimati hanno dato il loro prezioso contributo. L’intero codice in esecuzione nell’ultimo di questo blog dovrebbe rimuovere ogni confusione. Si tratta di un posto per il cinema che prenota un piccolo programma in uno scenario multi-thread.

Alcuni importanti dati elementari sono i seguenti. 1> Diversi thread possono contendere solo per istanze e variabili membro statiche nello spazio heap. 2> La lettura o scrittura volatile è completamente atomica e serializzata / avviene prima e viene eseguita 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 funziona 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 fa riferimento a un riferimento a un object. Questo riferimento è una variabile membro volatile nell’istanza AtomicReference come sotto.

 private volatile V value; 

get () restituisce semplicemente l’ultimo valore della variabile (come fanno i volatili in un modo “accade 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 (expect, update) chiama il metodo compareAndSwapObject () della class non sicura di Java. Questa chiamata al metodo di sicurezza richiama la chiamata nativa, che richiama una singola istruzione sul processore. “aspetta” e “aggiorna” ogni riferimento ad un object.

Se e solo se la variabile membro di istanza AtomicReference “value” fa riferimento allo stesso object viene definita da “expect”, “update” viene assegnato a questa variabile di istanza e “true” viene restituito. Oppure, viene restituito il 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 dei computer), è spesso più veloce rispetto all’utilizzo di un blocco sincronizzato. Ma ricorda che quando è necessario aggiornare più variabili atomicamente, AtomicReference non sarà di aiuto.

Vorrei aggiungere un codice in esecuzione completo, che può essere eseguito in eclipse. Risulterà 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 provando a prenotare 20 postazioni.

 for (int i = 0; i < 20; i++) {// 20 seats seats.add(new AtomicReference()); } 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.

 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> 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()); } 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 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> l;//seats int id;//id of the users int seats; public MyTh(List> 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 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 usiamo AtomicReference?

AtomicReference è un modo flessibile per aggiornare atomicamente il valore variabile senza utilizzare la sincronizzazione.

Supporto AtomicReference programmazione thread-safe senza lock su singole variabili.

Esistono diversi modi per raggiungere la sicurezza del thread con API simultanee di alto livello. Le variabili atomiche sono una delle molteplici opzioni.

Lock oggetti di blocco supportano idiomi di blocco che semplificano molte applicazioni concorrenti.

Executors definiscono un’API di alto livello per l’avvio e la gestione dei thread. Le implementazioni dell’esecutore 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 ampie raccolte di dati e possono ridurre notevolmente la necessità di sincronizzazione.

Le variabili atomiche hanno caratteristiche che riducono al minimo la sincronizzazione e aiutano a evitare errori di coerenza della memoria.

Fornire un semplice esempio in cui dovrebbe essere utilizzato AtomicReference.

Codice di esempio con AtomicReference :

 String initialReference = "value 1"; AtomicReference someRef = new AtomicReference(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.

Ecco un caso d’uso molto semplice e non ha nulla a che fare con la sicurezza dei thread.

Per condividere un object tra invocazioni lambda, AtomicReference è un’opzione :

 public void doSomethingUsingLambdas() { AtomicReference yourObjectRef = new AtomicReference<>(); soSomethingThatTakesALambda(() -> { yourObjectRef.set(youObject); }); soSomethingElseThatTakesALambda(() -> { YourObject yourObject = yourObjectRef.get(); }); } 

Non sto dicendo che questo è un buon design o altro (è solo un esempio banale), ma se si ha il caso in cui è necessario condividere un object tra invocazioni lambda, l’ AtomicReference è un’opzione.

In effetti puoi usare qualsiasi object che abbia un riferimento, anche una collezione che abbia un solo object. Tuttavia, AtomicReference è perfetto.

Un altro semplice esempio è eseguire una modifica del thread sicuro in un object di sessione.

 public PlayerScore getHighScore() { ServletContext ctx = getServletConfig().getServletContext(); AtomicReference holder = (AtomicReference) ctx.getAttribute("highScore"); return holder.get(); } public void updateHighScore(PlayerScore newScore) { ServletContext ctx = getServletConfig().getServletContext(); AtomicReference holder = (AtomicReference) 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