Softvérová transakčná pamäť v prostredí Java s využitím multiverse

1. Prehľad

V tomto článku sa pozrieme na Multivesmír knižnica - ktorá nám pomáha implementovať koncept Softvérová transakčná pamäť v Jave.

Pomocou konštrukcií z tejto knižnice môžeme vytvoriť synchronizačný mechanizmus v zdieľanom stave - čo je elegantnejšie a čitateľnejšie riešenie ako štandardná implementácia s jadrovou knižnicou Java.

2. Závislosť od Maven

Na začiatok budeme musieť pridať multiverse-core knižnica do nášho pom:

 org.multiverse multiverse-core 0.7.0 

3. Multiverse API

Začnime niekoľkými základmi.

Softvérová transakčná pamäť (STM) je koncept prenesený zo sveta databázy SQL - kde sa každá operácia vykonáva v rámci transakcií, ktoré vyhovujú KYSELINA (atómovosť, konzistencia, izolácia, trvanlivosť) vlastnosti. Tu, uspokojená je iba atomicita, konzistencia a izolácia, pretože mechanizmus beží v pamäti.

Hlavné rozhranie v knižnici Multiverse je TxnObject - každý transakčný objekt to musí implementovať a knižnica nám poskytuje množstvo špecifických podtried, ktoré môžeme použiť.

Každá operácia, ktorú je potrebné umiestniť do kritickej sekcie, je prístupná iba jedným vláknom a používa akýkoľvek transakčný objekt - je potrebné zabaliť do StmUtils.atomic () metóda. Kritická časť je miesto programu, ktoré nemôže byť spustené súčasne viac ako jedným vláknom, takže prístup k nemu by mal byť strážený nejakým synchronizačným mechanizmom.

Ak bude akcia v rámci transakcie úspešná, transakcia bude vykonaná a nový stav bude prístupný pre ďalšie vlákna. Ak dôjde k chybe, transakcia nebude vykonaná, a preto sa stav nezmení.

Nakoniec, ak chcú dve vlákna upraviť v transakcii rovnaký stav, uspeje iba jedno a vykoná svoje zmeny. Nasledujúce vlákno bude môcť vykonať svoju akciu v rámci svojej transakcie.

4. Implementácia logiky účtu pomocou STM

Poďme sa teraz pozrieť na príklad.

Povedzme, že chceme vytvoriť logiku bankového účtu pomocou STM poskytovaného Multivesmír knižnica. Náš Účet objekt bude mať lastUpadate časová známka, ktorá je a TxnLong typ a rovnováha pole, ktoré uchováva aktuálny zostatok pre daný účet a je z TxnInteger typu.

The TxnLong a TxnInteger sú triedy z Multivesmír. Musia sa vykonať v rámci transakcie. V opačnom prípade bude vyvolaná výnimka. Musíme použiť StmUtils na vytvorenie nových inštancií transakčných objektov:

účet verejnej triedy {private TxnLong lastUpdate; súkromný zostatok TxnInteger; public account (int balance) {this.lastUpdate = StmUtils.newTxnLong (System.currentTimeMillis ()); this.balance = StmUtils.newTxnInteger (zostatok); }}

Ďalej vytvoríme adjustBy () metóda - ktorá zvýši zostatok o danú sumu. Túto akciu je potrebné vykonať v rámci transakcie.

Ak sa do nej vloží akákoľvek výnimka, transakcia sa skončí bez vykonania akejkoľvek zmeny:

public void adjustBy (int suma) {adjustBy (amount, System.currentTimeMillis ()); } public void adjustBy (int amount, long date) {StmUtils.atomic (() -> {balance.increment (amount); lastUpdate.set (date); if (balance.get () <= 0) {throw new IllegalArgumentException ("Nedostatok peňazí"); } }); }

Ak chceme získať aktuálny zostatok pre daný účet, musíme získať hodnotu z poľa zostatok, ale je tiež potrebné ho vyvolať pomocou atómovej sémantiky:

public Integer getBalance () {return balance.atomicGet (); }

5. Testovanie účtu

Poďme vyskúšať Účet logika. Najskôr chceme z účtu zostatok znížiť o danú sumu jednoducho:

@Test public void givenAccount_whenDecrement_thenShouldReturnProperValue () {Účet a = nový účet (10); a.adjustBy (-5); assertThat (a.getBalance ()). isEqualTo (5); }

Ďalej povedzme, že z účtu vyberieme záporný zostatok. Táto akcia by mala spôsobiť výnimku a ponechať účet neporušený, pretože akcia bola vykonaná v rámci transakcie a nebola spáchaná:

@Test (očakáva sa = IllegalArgumentException.class) public void givenAccount_whenDecrementTooMuch_thenShouldThrow () {// daný účet a = nový účet (10); // keď a.adjustBy (-11); } 

Poďme teraz otestovať problém súbežnosti, ktorý môže vzniknúť, keď dve vlákna chcú znížiť rovnováhu súčasne.

Ak ho chce jedno vlákno znížiť o 5 a druhé o 6, jedna z týchto dvoch akcií by mala zlyhať, pretože aktuálny zostatok daného účtu sa rovná 10.

Ideme odoslať dve vlákna do ExecutorServicea použite CountDownLatch spustiť ich súčasne:

ExecutorService ex = Executors.newFixedThreadPool (2); Účet a = nový účet (10); CountDownLatch countDownLatch = nový CountDownLatch (1); AtomicBoolean exceptionThrown = nový AtomicBoolean (false); ex.submit (() -> {try {countDownLatch.await ();} catch (InterruptedException e) {e.printStackTrace ();} try {a.adjustBy (-6);} catch (IllegalArgumentException e) {exceptionThrown. set (true);}}); ex.submit (() -> {try {countDownLatch.await ();} catch (InterruptedException e) {e.printStackTrace ();} try {a.adjustBy (-5);} catch (IllegalArgumentException e) {exceptionThrown. množina (true);}});

Po zadaní obidvoch akcií naraz urobí jedna z nich výnimku:

countDownLatch.countDown (); ex.awaitTermination (1, TimeUnit.SECONDS); napr. vypnutie (); assertTrue (exceptionThrown.get ());

6. Prevod z jedného účtu do druhého

Povedzme, že chceme prevádzať peniaze z jedného účtu na druhý. Môžeme implementovať transferTo () metóda na Účet triedy obídením druhého Účet na ktorú chceme previesť dané množstvo peňazí:

public void transferTo (Účet iný, int čiastka) {StmUtils.atomic (() -> {long date = System.currentTimeMillis (); adjustBy (-amount, date); other.adjustBy (suma, dátum);}); }

Celá logika sa vykonáva v rámci transakcie. To zaručí, že keď chceme previesť čiastku, ktorá je vyššia ako zostatok na danom účte, oba účty budú neporušené, pretože transakcia sa nezaväzuje.

Vyskúšajme logiku prenosu:

Účet a = nový účet (10); Účet b = nový účet (10); a.transferTo (b, 5); assertThat (a.getBalance ()). isEqualTo (5); assertThat (b.getBalance ()). isEqualTo (15);

Jednoducho si vytvoríme dva účty, prevádzame peniaze z jedného na druhý a všetko funguje podľa očakávaní. Ďalej si povedzme, že chceme previesť viac peňazí, ako je k dispozícii na účte. The transferTo () hovor zahodí IllegalArgumentException, a zmeny nebudú vykonané:

skúsiť {a.transferTo (b, 20); } catch (IllegalArgumentException e) {System.out.println ("nepodarilo sa previesť peniaze"); } assertThat (a.getBalance ()). isEqualTo (5); assertThat (b.getBalance ()). isEqualTo (15);

Upozorňujeme, že zostatok pre obidve a a b účty sú rovnaké ako pred hovorom na server transferTo () metóda.

7. STM je zablokovanie bezpečné

Keď používame štandardný synchronizačný mechanizmus Java, naša logika môže byť náchylná na zablokovanie bez možnosti zotavenia sa z nich.

K zablokovaniu môže dôjsť, keď chceme previesť peniaze z účtu a na účet b. V štandardnej implementácii Java musí jedno vlákno zablokovať účet a, potom účet b. Povedzme, že medzitým chce druhé vlákno previesť peniaze z účtu b na účet a. Ostatné vlákno uzamkne účet b čakanie na účet a odomknúť.

Zámok pre účet a je držaný prvým vláknom a zámkom pre účet b je držaný druhým vláknom. Takáto situácia spôsobí, že náš program bude blokovaný na neurčito.

Našťastie pri implementácii transferTo () logika využívajúca STM, nemusíme sa obávať zablokovania, pretože STM je bezpečný proti zablokovaniu. Vyskúšajme to pomocou nášho transferTo () metóda.

Povedzme, že máme dve vlákna. Prvé vlákno chce previesť nejaké peniaze z účtu a na účet b, a druhé vlákno chce previesť nejaké peniaze z účtu b na účet a. Musíme vytvoriť dva účty a založiť dve vlákna, ktoré budú vykonávať transferTo () metóda v rovnakom čase:

ExecutorService ex = Executors.newFixedThreadPool (2); Účet a = nový účet (10); Účet b = nový účet (10); CountDownLatch countDownLatch = nový CountDownLatch (1); ex.submit (() -> {try {countDownLatch.await ();} catch (InterruptedException e) {e.printStackTrace ();} a.transferTo (b, 10);}); ex.submit (() -> {try {countDownLatch.await ();} catch (InterruptedException e) {e.printStackTrace ();} b.transferTo (a, 1);});

Po začatí spracovania budú mať oba účty správne pole zostatku:

countDownLatch.countDown (); ex.awaitTermination (1, TimeUnit.SECONDS); napr. vypnutie (); assertThat (a.getBalance ()). isEqualTo (1); assertThat (b.getBalance ()). isEqualTo (19);

8. Záver

V tomto tutoriáli sme sa pozreli na Multivesmír knižnice a na to, ako to môžeme použiť na vytvorenie bezzámkovej a bezpečnej logiky pomocou konceptov v softvérovej transakčnej pamäti.

Testovali sme správanie implementovanej logiky a zistili sme, že logika, ktorá používa STM, je bez zablokovania.

Implementáciu všetkých týchto príkladov a útržkov kódu nájdete v projekte GitHub - jedná sa o projekt Maven, takže by malo byť ľahké ho importovať a spustiť tak, ako je.


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