Cosa fa AtomicBoolean che un booleano volatile non può ottenere?
Cosa fa AtomicBoolean che un booleano volatile non può ottenere?
Risposte:
Sono solo totalmente diversi. Considera questo esempio di un volatile
numero intero:
volatile int i = 0;
void incIBy5() {
i += 5;
}
Se due thread chiamano contemporaneamente la funzione, i
potrebbero essere 5 in seguito, poiché il codice compilato sarà in qualche modo simile a questo (tranne che non è possibile sincronizzare su int
):
void incIBy5() {
int temp;
synchronized(i) { temp = i }
synchronized(i) { i = temp + 5 }
}
Se una variabile è volatile, ogni accesso atomico ad essa è sincronizzato, ma non è sempre ovvio ciò che effettivamente si qualifica come accesso atomico. Con un Atomic*
oggetto, è garantito che ogni metodo è "atomico".
Quindi, se usi un AtomicInteger
e getAndAdd(int delta)
, puoi essere sicuro che il risultato sarà 10
. Allo stesso modo, se due thread negano boolean
contemporaneamente una variabile, con un AtomicBoolean
puoi essere sicuro che abbia il valore originale in seguito, con un volatile boolean
, non puoi.
Quindi ogni volta che hai più di un thread che modifica un campo, devi renderlo atomico o usare la sincronizzazione esplicita.
Lo scopo di volatile
è diverso. Considera questo esempio
volatile boolean stop = false;
void loop() {
while (!stop) { ... }
}
void stop() { stop = true; }
Se hai un thread in esecuzione loop()
e un altro thread chiama stop()
, potresti ometterlo in un ciclo infinito se ometti volatile
, poiché il primo thread potrebbe memorizzare nella cache il valore di stop. Qui, volatile
serve come suggerimento al compilatore per essere un po 'più attento con le ottimizzazioni.
volatile
. La domanda è di circa volatile boolean
vs AtomicBoolean
.
volatile boolean
è sufficiente. Se ci sono anche molti scrittori, potresti averne bisogno AtomicBoolean
.
Uso campi volatili quando il campo è AGGIORNATO SOLO dal thread del proprietario e il valore viene letto solo da altri thread, puoi considerarlo uno scenario di pubblicazione / sottoscrizione in cui ci sono molti osservatori ma un solo editore. Tuttavia, se quegli osservatori devono eseguire una logica basata sul valore del campo e quindi respingere un nuovo valore, allora vado con Atomic * varianti o blocchi o blocchi sincronizzati, qualunque cosa mi si adatti meglio. In molti scenari simultanei si riduce per ottenere il valore, confrontarlo con un altro e aggiornarlo se necessario, quindi i metodi compareAndSet e getAndSet presenti nelle classi Atomic *.
Controlla i JavaDocs del pacchetto java.util.concurrent.atomic per un elenco di classi Atomic e un'eccellente spiegazione del loro funzionamento (ho appena appreso che sono senza blocchi, quindi hanno un vantaggio rispetto ai blocchi o ai blocchi sincronizzati)
boolean
var, dovremmo scegliere volatile boolean
.
Non puoi farlo compareAndSet
, getAndSet
come operazione atomica con valore booleano volatile (a meno che ovviamente non lo sincronizzi).
AtomicBoolean
ha metodi che eseguono le operazioni composte atomicamente e senza dover usare un synchronized
blocco. D'altra parte, volatile boolean
può eseguire operazioni composte solo se fatto all'interno di un synchronized
blocco.
Gli effetti di memoria della lettura / scrittura volatile boolean
sono identici ai metodi get
e set
di AtomicBoolean
rispettivamente.
Ad esempio, il compareAndSet
metodo eseguirà atomicamente quanto segue (senza un synchronized
blocco):
if (value == expectedValue) {
value = newValue;
return true;
} else {
return false;
}
Quindi, il compareAndSet
metodo ti permetterà di scrivere codice che è garantito per essere eseguito una sola volta, anche quando chiamato da più thread. Per esempio:
final AtomicBoolean isJobDone = new AtomicBoolean(false);
...
if (isJobDone.compareAndSet(false, true)) {
listener.notifyJobDone();
}
È garantito per notificare solo l'ascoltatore una volta (supponendo che non altri set di filo il AtomicBoolean
nuovo a false
nuovo dopo che sia impostato su true
).
volatile
la parola chiave garantisce la relazione prima dei thread che condividono quella variabile. Non ti garantisce che 2 o più thread non si interrompano a vicenda durante l'accesso a quella variabile booleana.
Atomic*
classe avvolge un volatile
campo.
Volatile booleano vs AtomicBoolean
Le classi Atomic * avvolgono una primitiva volatile dello stesso tipo. Dalla fonte:
public class AtomicLong extends Number implements java.io.Serializable {
...
private volatile long value;
...
public final long get() {
return value;
}
...
public final void set(long newValue) {
value = newValue;
}
Quindi, se tutto ciò che stai facendo è ottenere e impostare un Atomic *, potresti anche avere un campo volatile.
Cosa fa AtomicBoolean che un booleano volatile non può ottenere?
Le classi Atomic * offrono metodi che forniscono funzionalità più avanzate come incrementAndGet()
, compareAndSet()
e altre che implementano più operazioni (get / increment / set, test / set) senza blocco. Ecco perché le classi Atomic * sono così potenti.
Ad esempio, se più thread utilizzano il seguente codice utilizzando ++
, ci saranno condizioni di competizione perché ++
è effettivamente: get, increment, e set.
private volatile value;
...
// race conditions here
value++;
Tuttavia, il codice seguente funzionerà in un ambiente multi-thread sicuro senza blocchi:
private final AtomicLong value = new AtomicLong();
...
value.incrementAndGet();
È anche importante notare che il wrapping del campo volatile usando la classe Atomic * è un buon modo per incapsulare la risorsa condivisa critica dal punto di vista degli oggetti. Ciò significa che gli sviluppatori non possono limitarsi a gestire il campo supponendo che non sia condiviso, probabilmente iniettando problemi con un campo ++; o altro codice che introduce condizioni di gara.
Se ci sono più thread che accedono alla variabile a livello di classe, ogni thread può conservare una copia di quella variabile nella sua cache locale.
Rendere la variabile volatile impedirà ai thread di mantenere la copia della variabile nella cache dei thread locali.
Le variabili atomiche sono diverse e consentono la modifica atomica dei loro valori.
Il tipo primitivo booleano è atomico per le operazioni di scrittura e lettura, volatile garantisce il principio di successo. Quindi, se hai bisogno di un semplice get () e set (), allora non hai bisogno di AtomicBoolean.
D'altra parte, se è necessario implementare un controllo prima di impostare il valore di una variabile, ad esempio "se true, quindi impostare su false", è necessario eseguire anche questa operazione in modo atomico, in questo caso utilizzare compareAndSet e altri metodi forniti da AtomicBoolean, poiché se si tenta di implementare questa logica con valori booleani volatili è necessario un po 'di sincronizzazione per assicurarsi che il valore non sia cambiato tra get e set.
Ricorda l'IDIOM -
LEGGI - MODIFICA - SCRIVI ciò che non puoi ottenere con volatile
volatile
funziona solo nei casi in cui il thread proprietario ha la possibilità di aggiornare il valore del campo e gli altri thread possono solo leggere.
Se hai un solo thread che modifica il tuo booleano, puoi usare un booleano volatile (di solito lo fai per definire una stop
variabile controllata nel ciclo principale del thread).
Tuttavia, se hai più thread che modificano il valore booleano, dovresti usare un AtomicBoolean
. Altrimenti, il seguente codice non è sicuro:
boolean r = !myVolatileBoolean;
Questa operazione viene eseguita in due passaggi:
Se un altro thread modifica il valore tra #1
e 2#
, potresti ottenere un risultato errato. AtomicBoolean
i metodi evitano questo problema facendo passi #1
e #2
atomicamente.
Entrambi sono dello stesso concetto, ma in booleano atomico forniranno atomicità all'operazione nel caso in cui l'interruttore cpu avvenga nel mezzo.