Softverska transakcijska memorija u Javi pomoću Multiverse-a

1. Pregled

U ovom ćemo članku pogledati Multiverzum knjižnica - koja nam pomaže u provedbi koncepta Transakcijska memorija softvera na Javi.

Korištenjem konstrukcija iz ove knjižnice možemo stvoriti mehanizam sinkronizacije zajedničkog stanja - što je elegantnije i čitljivije rješenje od standardne implementacije s osnovnom bibliotekom Java.

2. Ovisnost Mavena

Za početak trebamo dodati multiverse-core knjižnica u naš pom:

 org.multiverzna multiverzalna jezgra 0.7.0 

3. Multiverse API

Krenimo s nekim osnovama.

Softverska transakcijska memorija (STM) koncept je prebačen iz svijeta SQL baze podataka - gdje se svaka operacija izvršava unutar transakcija koje zadovoljavaju KISELINA (atomskost, konzistencija, izolacija, trajnost) Svojstva. Ovdje, zadovoljeni su samo atomskost, dosljednost i izolacija jer se mehanizam pokreće u memoriji.

Glavno sučelje u knjižnici Multiverse je TxnObject - svaki transakcijski objekt mora ga implementirati, a knjižnica nam pruža niz određenih potklasa koje možemo koristiti.

Svaka operacija koju treba smjestiti u kritični odjeljak, kojoj treba pristupiti samo jedna nit i koja koristi bilo koji transakcijski objekt - mora biti omotana unutar StmUtils.atomic () metoda. Kritični odjeljak je mjesto programa koje ne može istodobno izvršavati više niti, pa bi pristup njemu trebao biti zaštićen nekim mehanizmom sinkronizacije.

Ako akcija unutar transakcije uspije, transakcija će se izvršiti, a novo će stanje biti dostupno ostalim nitima. Ako se dogodi neka pogreška, transakcija neće biti počinjena i stoga se stanje neće promijeniti.

Konačno, ako dvije niti žele modificirati isto stanje unutar transakcije, samo će jedna uspjeti i izvršiti svoje promjene. Sljedeća nit će moći izvršiti svoju radnju unutar svoje transakcije.

4. Implementacija logike računa pomoću STM-a

Pogledajmo sada jedan primjer.

Recimo da želimo stvoriti logiku bankovnog računa koristeći STM koji pruža Multiverzum knjižnica. Naše Račun objekt će imati lastUpadate vremenska oznaka koja ima TxnLong tip i ravnoteža polje koje pohranjuje trenutno stanje na danom računu, a nalazi se u TxnInteger tip.

The TxnLong i TxnInteger su razredi iz Multiverzum. Moraju se izvršiti u okviru transakcije. U suprotnom će se izuzeti. Moramo koristiti StmUtils za stvaranje novih primjeraka transakcijskih objekata:

račun javne klase {private TxnLong lastUpdate; privatna TxnInteger ravnoteža; javni račun (int saldo) {this.lastUpdate = StmUtils.newTxnLong (System.currentTimeMillis ()); this.balance = StmUtils.newTxnInteger (saldo); }}

Dalje ćemo stvoriti prilagoditiBy () metoda - koja će povećati saldo za zadani iznos. Tu radnju treba izvršiti unutar transakcije.

Ako se unutar nje ubaci bilo kakva iznimka, transakcija će završiti bez izvršavanja promjene:

javna praznina AdjuBy (int iznos) {AdjuBy (iznos, System.currentTimeMillis ()); } public void AdjuBy (int iznos, dugi datum) {StmUtils.atomic (() -> {balance.increment (iznos); lastUpdate.set (date); if (balance.get () <= 0) {baciti novi IllegalArgumentException ("Nedovoljno novca"); } }); }

Ako želimo dobiti trenutni saldo za zadani račun, trebamo dobiti vrijednost iz polja salda, ali ga također treba pozvati atomskom semantikom:

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

5. Testiranje računa

Isprobajmo svoje Račun logika. Prvo, želimo smanjiti saldo s računa za zadani iznos jednostavno:

@Test javna praznina givenAccount_whenDecrement_thenShouldReturnProperValue () {Račun a = novi račun (10); a.prilagodiBy (-5); assertThat (a.getBalance ()). isEqualTo (5); }

Dalje, recimo da se povlačimo s računa čineći saldo negativnim. Ta bi akcija trebala izuzeti i račun ostaviti netaknutim, jer je radnja izvršena unutar transakcije i nije počinjena:

@Test (očekuje se = IllegalArgumentException.class) javna praznina givenAccount_whenDecrementTooMuch_thenShouldThrow () {// zadani račun a = novi račun (10); // kada a.adjustBy (-11); } 

Isprobajmo sada problem paralelnosti koji se može pojaviti kada dvije niti žele istovremeno smanjiti saldo.

Ako ga jedna nit želi umanjiti za 5, a druga za 6, jedna od te dvije radnje trebala bi propasti jer je trenutačno stanje na danom računu jednako 10.

Predat ćemo dvije niti ExecutorServicei koristite CountDownLatch da ih pokrenete istodobno:

ExecutorService ex = Executors.newFixedThreadPool (2); Račun a = novi račun (10); CountDownLatch countDownLatch = novo CountDownLatch (1); AtomicBoolean exceptionThrown = novo AtomicBoolean (netačno); 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. set (true);}});

Nakon istodobnog zurenja u obje akcije, jedan od njih će izbaciti iznimku:

countDownLatch.countDown (); ex.awaitTermination (1, TimeUnit.SECONDS); npr.isključivanje (); assertTrue (exceptionThrown.get ());

6. Prijelaz s jednog računa na drugi

Recimo da želimo prebaciti novac s jednog računa na drugi. Možemo provesti transferTo () metoda na Račun razreda dodavanjem drugog Račun na koji želimo prenijeti zadani iznos novca:

javna void transferTo (Račun drugi, int iznos) {StmUtils.atomic (() -> {long date = System.currentTimeMillis (); prilagoditeBy (-iznos, datum); }

Sva se logika izvršava unutar transakcije. To će jamčiti da kada želimo prenijeti iznos koji je veći od stanja na danom računu, oba računa će biti netaknuta jer se transakcija neće obvezati.

Isprobajmo logiku prijenosa:

Račun a = novi račun (10); Račun b = novi račun (10); a.transferTo (b, 5); assertThat (a.getBalance ()). isEqualTo (5); assertThat (b.getBalance ()). isEqualTo (15);

Jednostavno otvorimo dva računa, novac prebacujemo s jednog na drugi i sve funkcionira prema očekivanjima. Dalje, recimo da želimo prenijeti više novca nego što je dostupno na računu. The transferTo () poziv će baciti IllegalArgumentException, i promjene neće biti počinjene:

pokušajte {a.transferTo (b, 20); } catch (IllegalArgumentException e) {System.out.println ("neuspješan prijenos novca"); } assertThat (a.getBalance ()). isEqualTo (5); assertThat (b.getBalance ()). isEqualTo (15);

Imajte na umu da ravnoteža za oboje a i b Računi su isti kao i prije poziva na transferTo () metoda.

7. STM je siguran u mrtvoj točki

Kada koristimo standardni mehanizam sinkronizacije Java, naša logika može biti sklona zastojima, bez načina da se oporavimo od njih.

Zastoj se može dogoditi kada želimo prebaciti novac s računa a na račun b. U standardnoj implementaciji Jave, jedna nit treba zaključati račun a, zatim račun b. Recimo da u međuvremenu druga nit želi prebaciti novac s računa b na račun a. Druga nit zaključava račun b čeka račun a otključati se.

Nažalost, zaključavanje računa a drži prva nit i brava za račun b drži druga nit. Takva će situacija uzrokovati blokiranje našeg programa na neodređeno vrijeme.

Srećom, prilikom implementacije transferTo () logikom koristeći STM, ne trebamo se brinuti o mrtvim točkama jer je STM siguran u mrtvoj točki. Isprobajmo to koristeći naš transferTo () metoda.

Recimo da imamo dvije niti. Prva nit želi prenijeti novac s računa a na račun b, a druga nit želi prebaciti nešto novca s računa b na račun a. Moramo stvoriti dva računa i pokrenuti dvije niti koje će izvršiti transferTo () metoda u isto vrijeme:

ExecutorService ex = Executors.newFixedThreadPool (2); Račun a = novi račun (10); Račun b = novi račun (10); CountDownLatch countDownLatch = novo 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);});

Nakon početka obrade, oba računa imat će odgovarajuće polje stanja:

countDownLatch.countDown (); ex.awaitTermination (1, TimeUnit.SECONDS); npr.isključivanje (); assertThat (a.getBalance ()). isEqualTo (1); assertThat (b.getBalance ()). isEqualTo (19);

8. Zaključak

U ovom vodiču pogledali smo Multiverzum knjižnice i kako to možemo koristiti za stvaranje logike bez zaključavanja i sigurne niti, koristeći koncepte u softverskoj transakcijskoj memoriji.

Testirali smo ponašanje implementirane logike i uvidjeli da logika koja koristi STM nema zastoja.

Provedbu svih ovih primjera i isječaka koda možete pronaći u projektu GitHub - ovo je Maven projekt, pa bi ga trebalo lako uvesti i pokrenuti kakav jest.


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