Variabili atomiche: aggiornamenti senza lock
Utilizzi AtomicInteger, AtomicLong e AtomicReference per contatori thread-safe senza lock.
Variabili atomiche: aggiornamenti senza lock è una lezione Java Academy gratuita su CoddyKit. Questa è la lezione 3 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento Java Academy, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso Java Academy include 4 lezioni in totale.
Il problema delle operazioni non atomiche
Il semplice incremento count++ NON è thread-safe: consiste di tre operazioni, lettura, addizione e scrittura. I thread concorrenti possono intercalare queste operazioni, causando la perdita di aggiornamenti. Le variabili atomiche risolvono il problema senza lock.
// UNSAFE:
int count = 0;
// Thread A reads count=0, Thread B reads count=0
// Both write 1 — second update lost!
count++; // NOT thread-safe
// SAFE:
import java.util.concurrent.atomic.*;
AtomicInteger atomicCount = new AtomicInteger(0);
atomicCount.incrementAndGet(); // atomic, lock-freeNozioni di base su AtomicInteger
AtomicInteger fornisce operazioni sugli interi garantite come atomiche, usando le istruzioni CPU compare-and-swap (CAS), senza bisogno di lock.
AtomicInteger counter = new AtomicInteger(0);
counter.set(10); // set to 10
System.out.println(counter.get()); // 10
int prev = counter.getAndIncrement(); // returns 10, sets to 11
int curr = counter.incrementAndGet(); // sets to 12, returns 12
counter.addAndGet(5); // adds 5, returns 17
counter.decrementAndGet(); // subtracts 1, returns 16compareAndSet: l'operazione CAS
compareAndSet(expected, update) aggiorna atomicamente il valore solo se attualmente è uguale al valore previsto. Restituisce true se l'operazione riesce. Questa è la base di tutti gli algoritmi lock-free.
AtomicInteger ai = new AtomicInteger(5);
boolean updated = ai.compareAndSet(5, 10); // current==5? set to 10
System.out.println(updated); // true
System.out.println(ai.get()); // 10
boolean failed = ai.compareAndSet(5, 20); // current==10, not 5 → fails
System.out.println(failed); // false
System.out.println(ai.get()); // still 10AtomicLong per i contatori
AtomicLong è l'equivalente a 64 bit. È usato comunemente per i generatori di sequenze e i contatori degli accessi:
AtomicLong seq = new AtomicLong(0);
// Thread-safe sequence generator:
long nextId() {
return seq.incrementAndGet();
}
System.out.println(nextId()); // 1
System.out.println(nextId()); // 2
System.out.println(nextId()); // 3AtomicBoolean
Flag booleano thread-safe. Viene usato comunemente per l'inizializzazione una tantum o per i segnali di arresto:
AtomicBoolean initialized = new AtomicBoolean(false);
void initOnce() {
// compareAndSet: only first caller succeeds
if (initialized.compareAndSet(false, true)) {
System.out.println("Initializing...");
// perform expensive init
} else {
System.out.println("Already initialized");
}
}AtomicReference
AtomicReference<V> fornisce aggiornamenti atomici a un riferimento a oggetto. Viene usato per implementare strutture dati lock-free:
import java.util.concurrent.atomic.*;
record Config(String host, int port) {}
AtomicReference<Config> configRef =
new AtomicReference<>(new Config("localhost", 8080));
// Hot-swap the configuration atomically:
Config oldConfig = configRef.get();
Config newConfig = new Config("prod.example.com", 443);
boolean swapped = configRef.compareAndSet(oldConfig, newConfig);
System.out.println(swapped); // true
System.out.println(configRef.get()); // Config[host=prod.example.com, port=443]updateAndGet / getAndUpdate
Applicare una funzione atomicamente usando internamente un ciclo di tentativi CAS:
AtomicInteger value = new AtomicInteger(10);
// Apply function atomically: square the value
int newVal = value.updateAndGet(x -> x * x);
System.out.println(newVal); // 100
// Get old, then apply:
int old = value.getAndUpdate(x -> x + 1);
System.out.println(old); // 100
System.out.println(value.get()); // 101accumulateAndGet
Combinare il valore corrente con un valore specificato usando una funzione:
AtomicInteger max = new AtomicInteger(0);
// Keep running maximum:
max.accumulateAndGet(42, Math::max); // max(0, 42) = 42
max.accumulateAndGet(15, Math::max); // max(42, 15) = 42
max.accumulateAndGet(99, Math::max); // max(42, 99) = 99
System.out.println(max.get()); // 99LongAdder per contatori soggetti a forte contesa
In presenza di forte contesa tra thread, i tentativi CAS di AtomicLong possono ridurre le prestazioni. LongAdder usa più celle per ridurre la contesa:
import java.util.concurrent.atomic.*;
LongAdder adder = new LongAdder();
// Multiple threads can call add() with minimal contention:
adder.increment();
adder.add(5);
adder.increment();
System.out.println(adder.sum()); // 7
// Note: sum() is NOT atomic with add() — use only when no concurrent addsArray atomici
AtomicIntegerArray / AtomicLongArray forniscono operazioni atomiche sui singoli elementi di un array:
AtomicIntegerArray arr = new AtomicIntegerArray(5);
arr.set(0, 10);
arr.incrementAndGet(0);
arr.compareAndSet(2, 0, 42); // index 2: if 0, set 42
System.out.println(arr.get(0)); // 11
System.out.println(arr.get(2)); // 42Quando usare le variabili atomiche invece dei lock
Usare variabili atomiche quando:
- Gli aggiornamenti riguardano una singola variabile (contatori, flag o riferimenti)
- La contesa è ridotta
- Le prestazioni lock-free sono importanti
Usare i lock quando:
- È necessario aggiornare atomicamente più variabili
- È necessaria una logica condizionale complessa
- La contesa è molto elevata (i tentativi CAS riducono le prestazioni)
Verifica rapida
Che cosa garantisce compareAndSet(expected, update)?
Riepilogo: variabili atomiche
Punti chiave:
- I tipi atomici usano istruzioni CPU CAS: sono lock-free e thread-safe
- AtomicInteger/Long/Boolean/Reference garantiscono l'atomicità su una singola variabile
- compareAndSet è la base degli algoritmi lock-free
- updateAndGet/accumulateAndGet consentono aggiornamenti funzionali
- LongAdder è adatto agli scenari con contatori soggetti a forte contesa
Domande Frequenti
La lezione «Variabili atomiche: aggiornamenti senza lock» è gratuita?
Sì — il testo completo di «Variabili atomiche: aggiornamenti senza lock» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso Java Academy, passa a CoddyKit PRO. Il corso Java Academy include 4 lezioni in totale.
Cosa imparerò in «Variabili atomiche: aggiornamenti senza lock»?
Utilizzi AtomicInteger, AtomicLong e AtomicReference per contatori thread-safe senza lock. Eserciti Java Academy con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.
Ho bisogno di esperienza per iniziare Java Academy?
Non è richiesta alcuna esperienza precedente. Java Academy su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 3 di 4.
Quanto tempo richiede la lezione «Variabili atomiche: aggiornamenti senza lock»?
La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.
Posso scrivere ed eseguire codice in questa lezione Java Academy?
Sì. Ogni lezione Java Academy include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.
Tutte le lezioni di questo corso
- ReentrantLock e synchronized a confronto
- ReadWriteLock per scenari lettore-scrittore
- Variabili atomiche: aggiornamenti senza lock
- StampedLock e letture ottimistiche