Úvod do atómových premenných v Jave

1. Úvod

Jednoducho povedané, zdieľaný premenlivý stav veľmi ľahko vedie k problémom, keď ide o súbežnosť. Ak prístup k zdieľaným premenlivým objektom nie je správne spravovaný, môžu sa aplikácie rýchlo stať náchylnými na niektoré ťažko zistiteľné chyby súbežnosti.

V tomto článku sa znova pozrieme na použitie zámkov na zvládnutie súbežného prístupu, preskúmame niektoré nevýhody spojené so zámkami a nakoniec predstavíme atómové premenné ako alternatívu.

2. Zámky

Poďme sa pozrieť na triedu:

public class Counter {int counter; public void increment () {counter ++; }}

V prípade prostredia s jedným vláknom to funguje perfektne; akonáhle však povolíme zápis viacerým vláknam, začneme dostávať nekonzistentné výsledky.

Je to z dôvodu jednoduchej operácie prírastku (pult ++), ktorý môže vyzerať ako atómová operácia, ale v skutočnosti ide o kombináciu troch operácií: získanie hodnoty, prírastok a zápis aktualizovanej hodnoty späť.

Ak sa dve vlákna pokúsia získať a aktualizovať hodnotu súčasne, môže to mať za následok stratu aktualizácií.

Jedným zo spôsobov riadenia prístupu k objektu je použitie zámkov. To sa dá dosiahnuť použitím synchronizované kľúčové slovo v prírastok podpis metódy. The synchronizované kľúčové slovo zaisťuje, že do metódy môže naraz vstúpiť iba jedno vlákno (ďalšie informácie o uzamknutí a synchronizácii nájdete v časti - Sprievodca synchronizovaným kľúčovým slovom v jazyku Java):

public class SafeCounterWithLock {private volatile int counter; public synchronized void increment () {counter ++; }}

Ďalej musíme pridať prchavý kľúčové slovo na zabezpečenie správnej viditeľnosti odkazov medzi vláknami.

Problém je vyriešený použitím zámkov. Predstavenie však stojí za to.

Keď sa viac vlákien pokúsi získať zámok, jedno z nich vyhrá, zatiaľ čo ostatné vlákna sú blokované alebo pozastavené.

Proces pozastavenia a následného obnovenia vlákna je veľmi nákladný a ovplyvňuje celkovú efektívnosť systému.

V malom programe, napríklad pult, čas strávený prepínaním kontextu sa môže stať oveľa viac ako skutočné vykonávanie kódu, čím sa výrazne zníži celková účinnosť.

3. Atómové operácie

Existuje odvetvie výskumu zamerané na vytváranie neblokujúcich algoritmov pre súbežné prostredia. Tieto algoritmy využívajú na zaistenie integrity údajov pokyny pre atómové stroje na nízkej úrovni, ako je napríklad porovnávanie a výmena (CAS).

Typická operácia CAS funguje na troch operandoch:

  1. Pamäťové miesto, na ktorom sa má pracovať (M)
  2. Existujúca očakávaná hodnota (A) premennej
  3. Je potrebné nastaviť novú hodnotu (B)

Operácia CAS atomicky aktualizuje hodnotu v M na B, ale iba ak sa existujúca hodnota v M zhoduje s A, inak sa nepodnikne žiadna akcia.

V obidvoch prípadoch sa vráti existujúca hodnota v M. To kombinuje tri kroky - získanie hodnoty, porovnanie hodnoty a aktualizácia hodnoty - do jednej operácie na úrovni stroja.

Keď sa niekoľko vlákien pokúsi aktualizovať rovnakú hodnotu prostredníctvom CAS, jedno z nich vyhrá a aktualizuje hodnotu. Avšak na rozdiel od zámkov sa nezastaví žiadne iné vlákno; namiesto toho sú jednoducho informovaní, že nestihli aktualizovať hodnotu. Vlákna potom môžu pokračovať v ďalšej práci a kontextovým prepínačom sa úplne zabráni.

Ďalším dôsledkom je, že logika základného programu sa stáva zložitejšou. Je to tak preto, lebo musíme zvládnuť scenár, keď operácia CAS neuspela. Môžeme to skúsiť znova a znova, kým to nebude úspešné, alebo môžeme neurobiť nič a pokračovať v závislosti od prípadu použitia.

4. Atómové premenné v prostredí Java

Najbežnejšie používané triedy atómových premenných v Jave sú AtomicInteger, AtomicLong, AtomicBoolean a AtomicReference. Tieto triedy predstavujú int, dlho, boolean, a odkaz na objekt, ktoré je možné atómovo aktualizovať. Hlavné metódy vystavené týmito triedami sú:

  • dostať () - získava hodnotu z pamäte, takže sú viditeľné zmeny vykonané inými vláknami; ekvivalent čítania a prchavý premenná
  • sada () - zapíše hodnotu do pamäte, aby bola zmena viditeľná pre iné vlákna; ekvivalent písania a prchavý premenná
  • lazySet () - prípadne zapíše hodnotu do pamäte, možno doobjedná s následnými príslušnými operáciami s pamäťou. Jedným z prípadov použitia sú anulovanie odkazov kvôli zberu odpadu, ku ktorému už nikdy nebudeme mať prístup. V takom prípade sa lepší výkon dosiahne oneskorením nuly prchavý napíš
  • compareAndSet () - rovnako ako je to popísané v časti 3, vráti true, keď bude úspešné, inak false
  • strongCompareAndSet () - to isté, čo je popísané v časti 3, ale slabšie v tom zmysle, že nevytvára deje pred objednávkami. To znamená, že nemusí nevyhnutne vidieť aktualizácie vykonané v iných premenných. Od verzie Java 9 bola táto metóda vo všetkých atómových implementáciách zastaraná v prospech strongCompareAndSetPlain (). Pamäťové efekty strongCompareAndSet () boli obyčajné, ale z ich mien vyplynuli účinky volatilnej pamäte. Aby sa tomuto nedorozumeniu vyhli, túto metódu zastarali a pridali štyri metódy s rôznymi pamäťovými efektmi ako napr strongCompareAndSetPlain () alebo strongCompareAndSetVolatile ()

Počítadlo bezpečné pre vlákna implementované s AtomicInteger je uvedený v príklade nižšie:

public class SafeCounterWithoutLock {private final AtomicInteger counter = new AtomicInteger (0); public int getValue () {return counter.get (); } public void increment () {while (true) {int existingValue = getValue (); int newValue = existingValue + 1; if (counter.compareAndSet (existingValue, newValue)) {návrat; }}}}

Ako vidíte, pokus zopakujeme porovnaj a nastav prevádzky a opäť pri poruche, pretože chceme zaručiť, že hovor na prírastok metóda vždy zvyšuje hodnotu o 1.

5. Záver

V tomto rýchlom výučbe sme opísali alternatívny spôsob riešenia súbežnosti, pri ktorom je možné vyhnúť sa nevýhodám spojeným s blokovaním. Pozreli sme sa tiež na hlavné metódy vystavené triedam atómových premenných v Jave.

Ako vždy, všetky príklady sú k dispozícii na GitHub.

Ak chcete preskúmať viac tried, ktoré interne používajú neblokujúce algoritmy, pozrite si príručku k aplikácii ConcurrentMap.


$config[zx-auto] not found$config[zx-overlay] not found