Cosa significa “atomico” nella programmazione?

Nel libro Effective Java, afferma:

La specifica del linguaggio garantisce che la lettura o la scrittura di una variabile sia atomica a meno che la variabile non sia di tipo long o double [JLS, 17.4.7].

Cosa significa “atomico” nel contesto della programmazione Java o della programmazione in generale?

Ecco un esempio, perché un esempio è spesso più chiaro di una lunga spiegazione. Supponiamo che foo sia una variabile di tipo long . La seguente operazione non è un’operazione atomica:

 foo = 65465498L; 

Infatti, la variabile viene scritta usando due operazioni separate: una che scrive i primi 32 bit e una seconda che scrive gli ultimi 32 bit. Ciò significa che un altro thread potrebbe leggere il valore di foo e vedere lo stato intermedio.

Rendere l’operazione atomica consiste nell’usare meccanismi di sincronizzazione per assicurarsi che l’operazione sia vista, da qualsiasi altro thread, come un’operazione singola, atomica (cioè non divisibile in parti). Ciò significa che qualsiasi altro thread, una volta che l’operazione è stata resa atomica, vedrà il valore di foo prima dell’assegnazione o dopo l’assegnazione. Ma mai il valore intermedio.

Un modo semplice per farlo è rendere la variabile volatile :

 private volatile long foo; 

O per sincronizzare ogni accesso alla variabile:

 public synchronized void setFoo(long value) { this.foo = value; } public synchronized long getFoo() { return this.foo; } // no other use of foo outside of these two methods, unless also synchronized 

O per sostituirlo con un AtomicLong :

 private AtomicLong foo; 

“Operazione atomica” indica un’operazione che sembra essere istantanea dalla prospettiva di tutti gli altri thread. Non è necessario preoccuparsi di un’operazione parzialmente completa quando si applica la garanzia.

È qualcosa che “sembra che il resto del sistema si verifichi istantaneamente” e rientra nella categorizzazione di Linearizzabilità nei processi di calcolo. Per citare ulteriormente questo articolo collegato:

L’atomicità è garanzia di isolamento dai processi concorrenti. Inoltre, le operazioni atomiche hanno comunemente una definizione di tipo “fail-or-fail”: cambiano con successo lo stato del sistema o non hanno alcun effetto apparente.

Quindi, ad esempio, nel contesto di un sistema di database, si può avere un ‘commit atomico’, nel senso che si può spingere un changeset di aggiornamenti in un database relazionale e tali modifiche saranno tutte inoltrate o nessuna di esse in nessun modo in l’evento di fallimento, in questo modo i dati non vengono corrotti e, conseguentemente, i blocchi e / o le code, la prossima operazione sarà una scrittura o una lettura diversa, ma solo dopo il fatto. Nel contesto di variabili e threading questo è più o meno lo stesso, applicato alla memoria.

La tua citazione sottolinea che non è necessario comportarsi in tutti i casi.

Nella filosofia antica, un atomo era l’ultima unità di materia sulla quale si basavano le visioni più complesse della realtà materiale. Nella programmazione per computer, l’atomico descrive un’azione o un object unitario che è essenzialmente indivisibile, immutabile, intero e irriducibile. Ecco alcuni usi:

1) In Structured Query Language, una funzione atomica è quella che completa o ritorna al suo stato originale se si verifica un’interruzione di corrente o una fine anomala.

2) In alcuni sistemi operativi di base Unix, un’operazione atomica è quella in cui nessuna modifica può avvenire nel tempo tra l’impostazione di una maschera e la ricezione di un segnale per cambiare la maschera.

3) In alcuni linguaggi di programmazione, incluso il Lisp, un atomo è l’unità di base del codice o dei dati eseguibili.

Un’operazione durante la quale un processore può leggere contemporaneamente una posizione e scriverla nella stessa operazione di bus. Ciò impedisce a qualsiasi altro processore o dispositivo I / O di scrivere o leggere memoria fino al completamento dell’operazione.

Atomico implica indivisibilità e irriducibilità, quindi un’operazione atomica deve essere eseguita interamente o non eseguita affatto.

Ho appena trovato un post Atomic vs. Non Atomic Operations per essere molto utile per me.

“Un’operazione che agisce sulla memoria condivisa è atomica se completa in un unico passaggio rispetto ad altri thread.

Quando un archivio atomico viene eseguito su una memoria condivisa, nessun altro thread può osservare la modifica completa a metà.

Quando un carico atomico viene eseguito su una variabile condivisa, legge l’intero valore come appariva in un singolo momento nel tempo. ”

Se hai diversi thread che eseguono i metodi m1 e m2 nel codice qui sotto:

 class SomeClass { private int i = 0; public void m1() { i = 5; } public int m2() { return i; } } 

hai la garanzia che qualsiasi thread che chiama m2 leggerà 0 o 5.

D’altra parte, con questo codice (dove sono un lungo):

 class SomeClass { private long i = 0; public void m1() { i = 1234567890L; } public long m2() { return i; } } 

un thread che chiama m2 potrebbe leggere 0, 1234567890L, o qualche altro valore casuale perché l’istruzione i = 1234567890L non è garantita per essere atomica per un long (una JVM potrebbe scrivere i primi 32 bit e gli ultimi 32 bit in due operazioni e una discussione potrebbe osservare i in mezzo).