Pregled atomskih klasa operacija (AtomicInteger) u Java atomic paketu
Ranije smo opisali CAS; nadamo se da se još sećate. Java atomske klase operacija, poput AtomicInteger i AtomicLong, na donjem nivou koriste CAS da osiguraju atomičnost ažuriranja promenljivih.
Operacija uvećanja poput count++ nije atomska; u višenitnoj sredini ne daje pravilan rezultat, jer se count++ sastoji iz tri koraka:
- Pročitati vrednost promenljive count;
- Uvećati vrednost promenljive count za 1;
- Upisati vrednost promenljive count u memoriju;
Pretpostavimo da nit A menja promenljivu count; da bismo osigurali da nit B pri korišćenju count vidi stanje koje je nakon izmene niti A, možemo koristiti ključnu reč synchronized da sinhronizujemo.
private long count = 0;
public synchronized void write() {
System.out.println("Pola života sam tražio proleće, tvoj osmeh je to.");
count++;
}Ali pristup metodi write() iz više niti je međusobno isključiv: nit B mora sačekati da nit A završi pristup. Ima li boljeg načina?
AtomicInteger je klasa atomske operacije nad Integer koju pruža JDK; njene operacije uvećanja i umanjenja su nit-bezbedne. Zato možemo:
private AtomicInteger count = new AtomicInteger(0);
public void write() {
System.out.println("Pola života sam tražio proleće, tvoj osmeh je to.");
count.incrementAndGet();
}Eto, sada je mnogo udobnije — bez zaključavanja se postiže nit-bezbednost. U redu, pogledajmo koje sve klase atomskih operacija postoje.
Atomski osnovni tipovi podataka
Glavni atomski osnovni tipovi su:
- AtomicBoolean: atomično ažurira boolean;
- AtomicInteger: atomično ažurira Integer;
- AtomicLong: atomično ažurira Long;
Korišćenje ovih klasa je uglavnom isto; ovde ćemo uzeti AtomicInteger kao primer.
addAndGet(int delta): dodaje zadati delta i vraća novu vrednost.incrementAndGet(): uvećava za 1 i vraća novu vrednost.getAndSet(int newValue): vraća trenutnu vrednost i postavlja novu vrednost na newValue.getAndIncrement(): vraća trenutnu vrednost i uvećava je za 1.
Postoje i druge metode; možete ih direktno pogledati u API-ju; sve su lako razumljive.

Da bismo razumeli princip realizacije AtomicInteger-a, pogledajmo izvorni kod metode getAndIncrement:
public final int getAndIncrement() {
// Koristi getAndAddInt metodu iz klase Unsafe da atomično uveća trenutnu vrednost AtomicInteger-a
// Prvi argument this jeste trenutna instanca AtomicInteger-a
// Drugi argument valueOffset je ofset koji ukazuje na poziciju u objektu AtomicInteger-a na kojoj se nalazi stvarna int vrednost
// Treći argument 1 je vrednost koja se dodaje na trenutnu vrednost (dakle iznos uvećanja)
// Metoda vraća originalnu vrednost pre uvećanja
return unsafe.getAndAddInt(this, valueOffset, 1);
}Vidimo da ova metoda zapravo poziva metodu getAndAddInt objekta unsafe, koji se dobija preko statičke metode getUnsafe klase Unsafe:
private static final Unsafe unsafe = Unsafe.getUnsafe();O klasi Unsafe smo govorili i u vezi sa CAS-om, uključujući klasu AtomicInteger; nadamo se da se sećate.
Klasa Unsafe je specijalna klasa u Javi za izvođenje niskih, nesigurnih operacija. Metoda getAndIncrement koristi CAS (Compare-And-Swap) operaciju iz klase Unsafe da ostvari atomičnu operaciju uvećanja. CAS je uobičajena tehnika bez brava koja omogućava atomično ažuriranje vrednosti u višenitnoj sredini.
Dobro, evo jednostavnog primera koji ilustruje upotrebu AtomicInteger-a:
public class AtomicDemo {
private static AtomicInteger atomicInteger = new AtomicInteger(1);
public static void main(String[] args) {
System.out.println(atomicInteger.getAndIncrement());
System.out.println(atomicInteger.get());
}
}Izlaz:
1
2Princip realizacije AtomicLong je uglavnom isti kao AtomicInteger-a; samo što se jedan odnosi na long, a drugi na int.
Kako klasa AtomicBoolean vrši ažuriranje? Ključna metoda je compareAndSet, čiji je izvorni kod ispod:
public final boolean compareAndSet(boolean expect, boolean update) {
// expect se prevodi u ceo broj: true -> 1, false -> 0
int e = expect ? 1 : 0;
// update se prevodi u ceo broj: true -> 1, false -> 0
int u = update ? 1 : 0;
// Koristi compareAndSwapInt metodu iz klase Unsafe da pokuša atomično ažuriranje trenutne vrednosti AtomicBoolean-a
// Prvi argument this jeste trenutna instanca AtomicBoolean-a
// Drugi argument valueOffset je ofset koji ukazuje na poziciju u objektu AtomicBoolean-a na kojoj se nalazi stvarna int vrednost
// Treći argument e je očekivana trenutna vrednost (nakon konverzije u ceo broj)
// Četvrti argument u je vrednost koju želimo da postavimo (nakon konverzije u ceo broj)
// Ako je trenutna vrednost jednaka očekivanoj e, atomično se postavlja na u i vraća true; inače vraća false.
return unsafe.compareAndSwapInt(this, valueOffset, e, u);
}Ova metoda pokušava da promeni trenutnu vrednost iz expect u update, ali će uspeti samo ako je trenutna vrednost zaista expect. Vraća true ako je ažuriranje uspelo, inače false.
Atomski tipovi nizova
Ako je potrebno atomično ažurirati element u nizu, atomic nudi i odgovarajuće klase:
- AtomicIntegerArray: ova klasa pruža metode za atomično ažuriranje nizova int vrednosti.
- AtomicLongArray: ova klasa pruža metode za atomično ažuriranje nizova long vrednosti.
- AtomicReferenceArray: ova klasa pruža metode za atomično ažuriranje nizova referentnog tipa.
Korišćenje ovih klasa je isto; ovde ćemo preko AtomicIntegerArray sumirati često korištene metode:
addAndGet(int i, int delta): atomično dodaje ulaznu vrednost elementu na indeksu i u nizu;getAndIncrement(int i): atomično uvećava za 1 element na indeksu i u nizu;compareAndSet(int i, int expect, int update): ažurira element na indeksu i u nizu.
Vidimo da su metode AtomicIntegerArray-a uglavnom iste kao AtomicInteger-a, osim što metode AtomicIntegerArray-a imaju dodatni indeks niza i. Jednostavan primer:
public class AtomicDemo {
// private static AtomicInteger atomicInteger = new AtomicInteger(1);
private static int[] value = new int[]{1, 2, 3};
private static AtomicIntegerArray integerArray = new AtomicIntegerArray(value);
public static void main(String[] args) {
//dodaj 5 na element na indeksu 1
int result = integerArray.getAndAdd(1, 5);
System.out.println(integerArray.get(1));
System.out.println(result);
}
}Izlaz:
7
2Preko metode getAndAdd na element na poziciji 1 dodata je vrednost 5; iz rezultata se vidi da je element na indeksu 1 postao 7; metoda vraća vrednost pre sabiranja, koja je 2.
Atomski referentni tipovi
Ako je potrebno atomično ažurirati referentni tip, atomic pruža sledeće klase:
- AtomicReference: atomično ažurira referentni tip;
- AtomicReferenceFieldUpdater: atomično ažurira polja unutar referentnog tipa;
- AtomicMarkableReference: atomično ažurira referentni tip sa oznakom;
I korišćenje ovih klasa je uglavnom isto; uzmimo AtomicReference kao primer da prikažemo osnovnu upotrebu. Demo ispod:
public class AtomicDemo {
private static AtomicReference<User> reference = new AtomicReference<>();
public static void main(String[] args) {
User user1 = new User("a", 1);
reference.set(user1);
User user2 = new User("b",2);
User user = reference.getAndSet(user2);
System.out.println(user);
System.out.println(reference.get());
}
static class User {
private String userName;
private int age;
public User(String userName, int age) {
this.userName = userName;
this.age = age;
}
@Override
public String toString() {
return "User{" +
"userName='" + userName + '\'' +
", age=" + age +
'}';
}
}
}Izlaz:
User{userName='a', age=1}
User{userName='b', age=2}Najpre se objekat User1 umota u AtomicReference, a zatim se pozivom getAndSet vrši dodela; iz rezultata se vidi da metoda atomično ažurira user objekat u User{userName='b', age=2}.
Atomsko ažuriranje tipova polja
Ako je potrebno ažurirati neko polje objekta, atomic pruža i odgovarajuće klase atomskih operacija:
- AtomicIntegerFieldUpdater: klasa za atomično ažuriranje polja tipa int;
- AtomicLongFieldUpdater: klasa za atomično ažuriranje polja tipa long;
- AtomicStampedReference: klasa za atomično ažuriranje referentnog tipa; ovo ažuriranje nosi verziju i služi da reši ABA problem CAS-a, o kome smo već govorili.
Atomsko ažuriranje polja zahteva dva koraka:
- Preko statičke metode
newUpdaterkreira se ažurerač i postavljaju klasa i polje koje želimo da ažuriramo; - Polje mora biti modifierovano sa
public volatile;
Uzmimo AtomicIntegerFieldUpdater kao primer da vidimo konkretnu upotrebu:
public class AtomicDemo {
private static AtomicIntegerFieldUpdater updater = AtomicIntegerFieldUpdater.newUpdater(User.class,"age");
public static void main(String[] args) {
User user = new User("a", 1);
int oldValue = updater.getAndAdd(user, 5);
System.out.println(oldValue);
System.out.println(updater.get(user));
}
static class User {
private String userName;
public volatile int age;
public User(String userName, int age) {
this.userName = userName;
this.age = age;
}
@Override
public String toString() {
return "User{" +
"userName='" + userName + '\'' +
", age=" + age +
'}';
}
}
}Izlaz:
1
6Iz primera se vidi da se AtomicIntegerFieldUpdater kreira preko svoje statičke metode; metod getAndAdd dodaje ulaznu vrednost na zadato polje i vraća vrednost pre sabiranja. Polje age u objektu user prvobitno je bilo 1, a nakon dodavanja 5 postalo je 6.
Zaključak
Java paket java.util.concurrent.atomic nudi niz klasa koje podržavaju atomske operacije (odnosno nit-bezbedne bez sinhronizacije) nad pojedinačnim promenljivima, što znatno smanjuje složenost istovremenog programiranja.
Glavne atomske klase operacija su:
- Atomski osnovni tipovi podataka: AtomicBoolean, AtomicInteger, AtomicLong;
- Atomski tipovi nizova: AtomicIntegerArray, AtomicLongArray, AtomicReferenceArray;
- Atomski referentni tipovi: AtomicReference, AtomicReferenceFieldUpdater, AtomicMarkableReference;
Urednik: Chenmo Wang Er; sadržaj pre uređivanja uglavnom potiče iz GitHub repozitorijuma CL0610 https://github.com/CL0610/Java-concurrency. Preporučeno čitanje: Detaljni prikaz klase Unsafe od Code Farmer Can Shang
