Transaktionel hukommelse til software i Java ved hjælp af Multiverse

1. Oversigt

I denne artikel ser vi på Multiverse bibliotek - som hjælper os med at implementere begrebet Transaktionel hukommelse til software i Java.

Ved hjælp af konstruktioner ud af dette bibliotek kan vi oprette en synkroniseringsmekanisme i delt tilstand - som er mere elegant og læselig løsning end standardimplementeringen med Java-kernebiblioteket.

2. Maven-afhængighed

For at komme i gang skal vi tilføje multivers-kerne bibliotek i vores pom:

 org.multiverse multiverse-core 0.7.0 

3. Multiverse API

Lad os starte med nogle af de grundlæggende.

Software Transactional Memory (STM) er et koncept, der overføres fra SQL-databaseverdenen - hvor hver operation udføres inden for transaktioner, der tilfredsstiller SYRE (Atomicitet, konsistens, isolering, holdbarhed) ejendomme. Her, kun Atomicitet, Konsistens og Isolation er tilfreds, fordi mekanismen kører i hukommelsen.

Hovedgrænsefladen i Multiverse-biblioteket er TxnObject - hvert transaktionsobjekt skal implementere det, og biblioteket giver os et antal specifikke underklasser, vi kan bruge.

Hver operation, der skal placeres i et kritisk afsnit, der kun er tilgængeligt af en tråd og bruger ethvert transaktionsobjekt - skal pakkes ind i StmUtils.atomic () metode. Et kritisk afsnit er et sted i et program, der ikke kan udføres af mere end en tråd samtidigt, så adgang til det skal beskyttes af en eller anden synkroniseringsmekanisme.

Hvis en handling inden for en transaktion lykkes, vil transaktionen blive begået, og den nye tilstand vil være tilgængelig for andre tråde. Hvis der opstår en fejl, begås transaktionen ikke, og staten vil derfor ikke ændre sig.

Endelig, hvis to tråde ønsker at ændre den samme tilstand inden for en transaktion, er det kun en, der lykkes og foretager sine ændringer. Den næste tråd vil være i stand til at udføre sin handling inden for sin transaktion.

4. Implementering af kontologik ved hjælp af STM

Lad os nu se på et eksempel.

Lad os sige, at vi vil oprette en bankkontologik ved hjælp af STM leveret af Multiverse bibliotek. Vores Konto objektet vil have lastUpadate tidsstempel, der er af en TxnLong type og balance felt, der gemmer den aktuelle saldo for en given konto og er af TxnInteger type.

Det TxnLong og TxnInteger er klasser fra Multiverse. De skal udføres inden for en transaktion. Ellers kastes en undtagelse. Vi er nødt til at bruge StmUtils for at oprette nye forekomster af transaktionsobjekterne:

offentlig klassekonto {privat TxnLong lastUpdate; privat TxnInteger-balance offentlig konto (int-saldo) {this.lastUpdate = StmUtils.newTxnLong (System.currentTimeMillis ()); this.balance = StmUtils.newTxnInteger (balance); }}

Derefter opretter vi justerBy () metode - som forøger saldoen med det givne beløb. Denne handling skal udføres inden for en transaktion.

Hvis en undtagelse kastes inde i den, afsluttes transaktionen uden at foretage nogen ændring:

offentlig tomrum justereBy (int beløb) {justerBy (beløb, System.currentTimeMillis ()); } public void adjustBy (int mængde, lang dato) {StmUtils.atomic (() -> {balance.increment (beløb); lastUpdate.set (dato); hvis (balance.get () <= 0) {smid nyt IllegalArgumentException ("Ikke nok penge"); } }); }

Hvis vi ønsker at få den aktuelle saldo for den givne konto, er vi nødt til at hente værdien fra balancefeltet, men den skal også påberåbes med atomsemantik:

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

5. Test af kontoen

Lad os teste vores Konto logik. For det første ønsker vi at reducere saldoen fra kontoen med det givne beløb blot:

@Test offentligt ugyldigt givenAccount_whenDecrement_thenShouldReturnProperValue () {Account a = new Account (10); a.adjustBy (-5); hævder, at (a.getBalance ()). er lig med (5); }

Lad os derefter sige, at vi trækker os fra kontoen, hvilket gør saldoen negativ. Denne handling burde give en undtagelse og lade kontoen være intakt, fordi handlingen blev udført inden for en transaktion og ikke blev begået:

@Test (forventet = IllegalArgumentException.class) offentlig ugyldighed givenAccount_whenDecrementTooMuch_thenShouldThrow () {// given konto a = ny konto (10); // når a.adjustBy (-11); } 

Lad os nu teste et samtidighedsproblem, der kan opstå, når to tråde ønsker at mindske en balance på samme tid.

Hvis en tråd ønsker at mindske den med 5 og den anden med 6, skal en af ​​disse to handlinger mislykkes, fordi den aktuelle saldo på den givne konto er lig med 10.

Vi sender to tråde til ExecutorService, og brug CountDownLatch at starte dem på samme tid:

ExecutorService ex = Executors.newFixedThreadPool (2); Konto a = ny konto (10); CountDownLatch countDownLatch = ny CountDownLatch (1); AtomicBoolean exceptionThrown = ny AtomicBoolean (falsk); ex.submit (() -> {prøv {countDownLatch.await ();} fangst (InterruptedException e) {e.printStackTrace ();} prøv {a.adjustBy (-6);} fangst (IllegalArgumentException e) {exceptionThrown. sæt (sand);}}); ex.submit (() -> {prøv {countDownLatch.await ();} fangst (InterruptedException e) {e.printStackTrace ();} prøv {a.adjustBy (-5);} fangst (IllegalArgumentException e) {exceptionThrown. sæt (sand);}});

Efter at have stirret på begge handlinger på samme tid, vil en af ​​dem kaste en undtagelse:

countDownLatch.countDown (); ex.awaitTermination (1, TimeUnit.SECONDS); f.eks. nedlukning (); assertTrue (exceptionThrown.get ());

6. Overførsel fra en konto til en anden

Lad os sige, at vi vil overføre penge fra den ene konto til den anden. Vi kan implementere Overfør til() metode til Konto klasse ved at passere den anden Konto som vi vil overføre det givne beløb til:

offentlig ugyldig transferTo (Konto andet, int beløb) {StmUtils.atomic (() -> {lang dato = System.currentTimeMillis (); justerBy (-beløb, dato); andet.adjustBy (beløb, dato);}); }

Al logik udføres inden for en transaktion. Dette vil garantere, at når vi vil overføre et beløb, der er højere end saldoen på den givne konto, vil begge konti være intakte, fordi transaktionen ikke forpligter sig.

Lad os teste overførsel af logik:

Konto a = ny konto (10); Konto b = ny konto (10); a.transferTo (b, 5); hævder, at (a.getBalance ()). er lig med (5); assertThat (b.getBalance ()). er EqualTo (15);

Vi opretter simpelthen to konti, vi overfører pengene fra den ene til den anden, og alt fungerer som forventet. Lad os derefter sige, at vi vil overføre flere penge, end der er tilgængelige på kontoen. Det Overfør til() opkald vil kaste UlovligtArgumentUndtagelse, og ændringerne vil ikke blive begået:

prøv {a.transferTo (b, 20); } fange (IllegalArgumentException e) {System.out.println ("kunne ikke overføre penge"); } hævder, at (a.getBalance ()). er lig med (5); assertThat (b.getBalance ()). er EqualTo (15);

Bemærk, at saldoen for begge -en og b konti er det samme som før opkaldet til Overfør til() metode.

7. STM er fastlåst

Når vi bruger standard Java-synkroniseringsmekanismen, kan vores logik være tilbøjelig til blokeringer uden nogen måde at komme sig fra dem.

Dødlåsen kan forekomme, når vi vil overføre pengene fra kontoen -en til konto b. I standard Java-implementering skal en tråd låse kontoen -en, derefter konto b. Lad os sige, at den anden tråd i mellemtiden vil overføre pengene fra kontoen b til konto -en. Den anden trådlåsningskonto b venter på en konto -en at blive låst op.

Desværre låsen til en konto -en holdes af den første tråd og låsen til konto b holdes af den anden tråd. En sådan situation vil få vores program til at blokere på ubestemt tid.

Heldigvis ved implementering Overfør til() logik ved hjælp af STM, behøver vi ikke bekymre os om deadlocks, da STM er Deadlock Safe. Lad os teste det ved hjælp af vores Overfør til() metode.

Lad os sige, at vi har to tråde. Første tråd ønsker at overføre nogle penge fra kontoen -en til konto b, og den anden tråd ønsker at overføre nogle penge fra kontoen b til konto -en. Vi er nødt til at oprette to konti og starte to tråde, der udfører Overfør til() metode på samme tid:

ExecutorService ex = Executors.newFixedThreadPool (2); Konto a = ny konto (10); Konto b = ny konto (10); CountDownLatch countDownLatch = ny CountDownLatch (1); ex.submit (() -> {prøv {countDownLatch.await ();} fang (InterruptedException e) {e.printStackTrace ();} a.transferTo (b, 10);}); ex.submit (() -> {prøv {countDownLatch.await ();} fang (InterruptedException e) {e.printStackTrace ();} b.transferTo (a, 1);});

Efter behandlingens start har begge konti det rette saldofelt:

countDownLatch.countDown (); ex.awaitTermination (1, TimeUnit.SECONDS); f.eks. nedlukning (); hævder, at (a.getBalance ()). er lig med (1); assertThat (b.getBalance ()). er EqualTo (19);

8. Konklusion

I denne vejledning kiggede vi på Multiverse bibliotek og hvordan vi kan bruge det til at skabe låsefri og trådsikker logik ved hjælp af koncepter i softwaretransaktionshukommelsen.

Vi testede opførslen af ​​den implementerede logik og så, at den logik, der bruger STM, er fastlåst.

Implementeringen af ​​alle disse eksempler og kodestykker findes i GitHub-projektet - dette er et Maven-projekt, så det skal være let at importere og køre, som det er.


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