Az atommƱveletek megjelenĂ©sĂ©nek elƑfeltĂ©telei

NĂ©zzĂŒk meg ezt a pĂ©ldĂĄt, hogy segĂ­tsen megĂ©rteni, hogyan mƱködnek az atomi mƱveletek:

public class Counter {
    int count;

    public void increment() {
        count++;
    }
}

Ha egy szĂĄlunk van, minden remekĂŒl mƱködik, de ha többszĂĄlat adunk hozzĂĄ, akkor rossz eredmĂ©nyt kapunk, Ă©s mindez azĂ©rt, mert a növelĂ©si mƱvelet nem egy mƱvelet, hanem hĂĄrom: egy kĂ©rĂ©s az aktuĂĄlis Ă©rtĂ©k lekĂ©rĂ©sĂ©re.szĂĄmol, majd növelje 1-gyel, Ă©s Ă­rjon Ășjra ideszĂĄmol.

És ha kĂ©t szĂĄl növelni akar egy vĂĄltozĂłt, akkor nagy valĂłszĂ­nƱsĂ©ggel adatvesztĂ©sre kerĂŒl sor. Vagyis mindkĂ©t szĂĄl 100-at kap, ennek eredmĂ©nyekĂ©nt mindkettƑ 101-et Ă­r a 102-es vĂĄrhatĂł Ă©rtĂ©k helyett.

És hogyan lehet megoldani? ZĂĄrakat kell hasznĂĄlni. A szinkronizĂĄlt kulcsszĂł segĂ­t megoldani ezt a problĂ©mĂĄt, hasznĂĄlata garantĂĄlja, hogy egy szĂĄl egyszerre Ă©ri el a metĂłdust.

public class SynchronizedCounterWithLock {
    private volatile int count;

    public synchronized void increment() {
        count++;
    }
}

EzenkĂ­vĂŒl hozzĂĄ kell adnia a volatile kulcsszĂłt , amely biztosĂ­tja a hivatkozĂĄsok megfelelƑ lĂĄthatĂłsĂĄgĂĄt a szĂĄlak között. MunkĂĄssĂĄgĂĄt fentebb ĂĄttekintettĂŒk.

De mĂ©g mindig vannak ĂĄrnyoldalai. A legnagyobb a teljesĂ­tmĂ©ny, amikor sok szĂĄl prĂłbĂĄl zĂĄrolĂĄst szerezni, Ă©s az egyik Ă­rĂĄsi lehetƑsĂ©get kap, a többi szĂĄl vagy blokkolva lesz, vagy felfĂŒggesztve lesz, amĂ­g a szĂĄl fel nem szabadul.

Mindezek a folyamatok, a blokkolåsok, a måsik ållapotba våltås nagyon költséges a rendszer teljesítménye szempontjåból.

AtommƱveletek

Az algoritmus alacsony szintƱ gĂ©pi utasĂ­tĂĄsokat hasznĂĄl, mint pĂ©ldĂĄul az összehasonlĂ­tĂĄs Ă©s csere (CAS, összehasonlĂ­tĂĄs Ă©s csere, amely biztosĂ­tja az adatok integritĂĄsĂĄt, Ă©s mĂĄr nagy mennyisĂ©gƱ kutatĂĄs folyik ezekrƑl).

Egy tipikus CAS-mƱvelet hårom operanduson mƱködik:

  • MemĂłria a munkĂĄhoz (M)
  • Egy vĂĄltozĂł meglĂ©vƑ vĂĄrhatĂł Ă©rtĂ©ke (A).
  • Új Ă©rtĂ©k (B) beĂĄllĂ­tandĂł

A CAS atomikusan frissĂ­ti M-et B-re, de csak akkor, ha M Ă©rtĂ©ke megegyezik A-val, kĂŒlönben nem törtĂ©nik semmi.

Az elsƑ Ă©s a mĂĄsodik esetben az M Ă©rtĂ©ke kerĂŒl visszaadĂĄsra, ami lehetƑvĂ© teszi hĂĄrom lĂ©pĂ©s kombinĂĄlĂĄsĂĄt, nevezetesen az Ă©rtĂ©k lekĂ©rĂ©sĂ©t, az Ă©rtĂ©k összehasonlĂ­tĂĄsĂĄt Ă©s frissĂ­tĂ©sĂ©t. És mindez egyetlen mƱvelettĂ© vĂĄlik a gĂ©p szintjĂ©n.

Abban a pillanatban, amikor egy többszĂĄlĂș alkalmazĂĄs hozzĂĄfĂ©r egy vĂĄltozĂłhoz, Ă©s megprĂłbĂĄlja frissĂ­teni, Ă©s a CAS alkalmazĂĄsra kerĂŒl, akkor az egyik szĂĄl megkapja Ă©s frissĂ­teni tudja. A zĂĄrolĂĄsokkal ellentĂ©tben azonban mĂĄs szĂĄlak egyszerƱen hibaĂŒzenetet kapnak arrĂłl, hogy nem tudjĂĄk frissĂ­teni az Ă©rtĂ©ket. EzutĂĄn ĂĄttĂ©rnek a tovĂĄbbi munkĂĄra, Ă©s a vĂĄltĂĄs teljesen kizĂĄrt az ilyen tĂ­pusĂș munkĂĄknĂĄl.

Ebben az esetben a logika megnehezĂŒl amiatt, hogy kezelnĂŒnk kell azt a helyzetet, amikor a CAS mƱvelet nem mƱködött sikeresen. Csak Ășgy modellezzĂŒk a kĂłdot, hogy ne haladjon tovĂĄbb, amĂ­g a mƱvelet nem sikerĂŒl.

Bevezetés az atomtípusokba

TalĂĄlkoztĂĄl mĂĄr olyan helyzettel, amikor be kell ĂĄllĂ­tani a szinkronizĂĄlĂĄst a legegyszerƱbb int tĂ­pusĂș vĂĄltozĂłhoz ?

Az elsƑ módszer, amelyet már tárgyaltunk, a volatile + synchronized használata . De vannak speciális Atomic* órák is.

Ha CAS-t hasznĂĄlunk, akkor a mƱveletek gyorsabban mƱködnek az elsƑ mĂłdszerhez kĂ©pest. Ezen kĂ­vĂŒl speciĂĄlis Ă©s nagyon kĂ©nyelmes mĂłdszereink vannak az Ă©rtĂ©k hozzĂĄadĂĄsĂĄra, valamint a növelĂ©si Ă©s csökkentĂ©sĂ©re.

Az AtomicBoolean , AtomicInteger , AtomicLong , AtomicIntegerArray , AtomicLongArray olyan osztĂĄlyok, amelyekben a mƱveletek atomi jellegƱek. Az alĂĄbbiakban elemezzĂŒk a velĂŒk vĂ©gzett munkĂĄt.

AtomicInteger

Az AtomicInteger osztåly mƱveleteket biztosít egy int értékkel , amely atomian olvasható és írható, a kiterjesztett atomi mƱveletek mellett.

Olyan beszerzĂ©si Ă©s beĂĄllĂ­tĂĄsi mĂłdszereket tartalmaz , amelyek Ășgy mƱködnek, mint a vĂĄltozĂłk olvasĂĄsa Ă©s Ă­rĂĄsa.

Vagyis „elƑtte törtĂ©nik” ugyanazon vĂĄltozĂł bĂĄrmely kĂ©sƑbbi beĂ©rkezĂ©sĂ©vel, amelyrƑl korĂĄbban beszĂ©ltĂŒnk. Az atomiccompleteAndSet metĂłdus is rendelkezik ezekkel a memĂłriakonzisztencia jellemzƑkkel.

Minden olyan mƱvelet, amely Ășj Ă©rtĂ©ket ad vissza, atomosan törtĂ©nik:

int addAndGet (int delta) Adott értéket ad az aktuålis értékhez.
logikai összehasonlítåsAndSet(elvårt int, frissítés int) Az értéket a megadott frissített értékre ållítja be, ha az aktuålis érték megegyezik a vårt értékkel.
int decrementAndGet() Eggyel csökkenti az aktuålis értéket.
int getAndAdd(int delta) A megadott értéket hozzåadja az aktuålis értékhez.
int getAndDecrement() Eggyel csökkenti az aktuålis értéket.
int getAndIncrement() Az aktuålis értéket eggyel növeli.
int getAndSet(int newValue) Beållítja a megadott értéket, és visszaadja a régi értéket.
int incrementAndGet() Az aktuålis értéket eggyel növeli.
lazySet(int newValue) VĂ©gĂŒl ĂĄllĂ­tsa be a megadott Ă©rtĂ©ket.
logikai gyengeCompareAndSet(vårható, frissítés int) Az értéket a megadott frissített értékre ållítja be, ha az aktuålis érték megegyezik a vårt értékkel.

Példa:

ExecutorService executor = Executors.newFixedThreadPool(5);
IntStream.range(0, 50).forEach(i -> executor.submit(atomicInteger::incrementAndGet));
executor.shutdown();
executor.awaitTermination(Long.MAX_VALUE, TimeUnit.HOURS);

System.out.println(atomicInteger.get()); // prints 50