Tipuri atomice
Ce sunt tipurile atomice?
Tipurile atomice sunt clase speciale din pachetul java.util.concurrent.atomic
care permit efectuarea operațiilor thread-safe pe variabile individuale, fără a necesita blocuri synchronized
sau alte mecanisme de blocare. O operație atomică este completă și indivizibilă - fie se execută complet, fie nu se execută deloc, fără a putea fi întreruptă de alte fire de execuție.
Aceste clase au fost introduse în Java 5 pentru a simplifica programarea concurentă și pentru a îmbunătăți performanța aplicațiilor multi-threading.
De ce avem nevoie de tipuri atomice?
Când mai multe fire de execuție încearcă să modifice simultan aceeași variabilă, pot apărea rezultate neașteptate. Să analizăm un exemplu simplu:
// Problemă clasică în mediu multi-threading
int counter = 0;
// Thread 1
counter++; // Această operație nu este atomică!
// Thread 2 (simultan)
counter++; // Poate interfera cu Thread 1
Incrementarea (counter++
) nu este o operație atomică, ci constă din trei operații separate:
Citirea valorii curente
Incrementarea valorii
Scrierea valorii înapoi în variabilă
Dacă două fire execută aceste operații în același timp, este posibil ca valoarea finală să fie incrementată doar o dată, nu de două ori cum ar fi de așteptat.
Tipurile atomice de bază
AtomicInteger
Permite operații atomice pe valori de tip int:
import java.util.concurrent.atomic.AtomicInteger;
AtomicInteger counter = new AtomicInteger(0); // Inițializare cu valoarea 0
// Incrementare atomică
counter.incrementAndGet(); // Echivalent atomic pentru ++counter
counter.getAndIncrement(); // Echivalent atomic pentru counter++
// Setare și obținere
counter.set(10);
int value = counter.get(); // Obține valoarea curentă
// Adunare atomică
counter.addAndGet(5); // Adaugă 5 și returnează noua valoare
AtomicLong
Similar cu AtomicInteger, dar pentru valori de tip long:
AtomicLong longCounter = new AtomicLong(0);
longCounter.incrementAndGet();
long result = longCounter.get();
AtomicBoolean
Operații atomice pe valori boolean:
AtomicBoolean flag = new AtomicBoolean(false);
flag.set(true);
boolean value = flag.get();
// Schimbă valoarea și returnează vechea valoare
boolean oldValue = flag.getAndSet(true);
// Schimbă valoarea doar dacă este cea așteptată
boolean success = flag.compareAndSet(false, true);
AtomicReference
Permite operații atomice pe referințe la obiecte:
User user = new User("John");
AtomicReference<User> userRef = new AtomicReference<>(user);
User oldUser = userRef.get();
User newUser = new User("Alice");
// Actualizează referința doar dacă este cea așteptată
userRef.compareAndSet(oldUser, newUser);
Operații atomice fundamentale
get() și set()
Citirea și scrierea atomică a valorilor:
AtomicInteger ai = new AtomicInteger(5);
int value = ai.get(); // Obține valoarea curentă atomic
ai.set(10); // Setează valoarea atomic
compareAndSet()
Actualizează valoarea doar dacă valoarea curentă este cea așteptată:
AtomicInteger ai = new AtomicInteger(5);
boolean success = ai.compareAndSet(5, 10); // Va returna true și va actualiza la 10
boolean fail = ai.compareAndSet(5, 20); // Va returna false (valoarea e acum 10)
Aceasta este operația fundamentală pe care se bazează majoritatea celorlalte operații atomice.
getAndUpdate() și updateAndGet()
Actualizează valoarea folosind o funcție și returnează valoarea veche sau nouă:
AtomicInteger ai = new AtomicInteger(5);
int oldValue = ai.getAndUpdate(x -> x * 2); // oldValue = 5, ai = 10
int newValue = ai.updateAndGet(x -> x + 1); // newValue = 11, ai = 11
Exemplu practic: Contor thread-safe
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class SimpleCounter {
private final AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet();
}
public int getCount() {
return count.get();
}
public static void main(String[] args) throws InterruptedException {
SimpleCounter counter = new SimpleCounter();
ExecutorService executor = Executors.newFixedThreadPool(5);
// Lansăm 1000 de task-uri care incrementează contorul
for (int i = 0; i < 1000; i++) {
executor.submit(counter::increment);
}
executor.shutdown();
executor.awaitTermination(5, TimeUnit.SECONDS);
System.out.println("Valoare finală: " + counter.getCount());
// Va afișa întotdeauna 1000, fiind thread-safe
}
}
Avantajele utilizării tipurilor atomice
Simplitate - Cod mai clar și mai succint față de sincronizarea tradițională
Performanță - Operații non-blocking, mai rapide decât blocurile synchronized
Siguranță - Garantează operații corecte în medii multi-threading
Scalabilitate - Permit un grad mai mare de concurență în aplicații
Când să folosiți tipurile atomice
Tipurile atomice sunt potrivite pentru:
Contoare simple și acumulatori în medii concurente
Flags și management de stare pentru sincronizarea între fire
Operații de citire-actualizare-scriere care trebuie să fie atomice
Valori partajate între mai multe fire de execuție
Pentru operații mai complexe care implică mai multe variabile, sau pentru sincronizarea între secvențe mai lungi de cod, ar putea fi necesare alte mecanisme precum blocurile sincronizate, lock-urile explicite sau colecțiile concurente.
Limitări
Tipurile atomice garantează atomicitatea doar pentru operații pe valori individuale. Pentru operații care implică mai multe variabile, ar trebui utilizate alte mecanisme de sincronizare.
Concluzii
Tipurile atomice din Java sunt instrumente esențiale pentru programarea concurentă, oferind operații thread-safe pe variabile individuale fără overhead-ul sincronizării tradiționale. Ele simplifică implementarea algoritmilor concurenți și îmbunătățesc performanța aplicațiilor multi-threading.
Utilizarea adecvată a acestor clase poate reduce substanțial complexitatea codului concurent și poate elimina multe erori asociate cu sincronizarea tradițională.
Last updated