Programvaretransaksjonsminne i Java ved bruk av multiverse

1. Oversikt

I denne artikkelen ser vi på Multiverse bibliotek - som hjelper oss med å implementere konseptet med Programvaretransaksjonsminne i Java.

Ved hjelp av konstruksjoner fra dette biblioteket kan vi lage en synkroniseringsmekanisme i delt tilstand - som er mer elegant og lesbar løsning enn standardimplementeringen med Java-kjernebiblioteket.

2. Maven avhengighet

For å komme i gang må vi legge til flerkjernekjerne bibliotek i vår pom:

 org.multiverse multiverse-core 0.7.0 

3. Multiverse API

La oss starte med noen av de grunnleggende.

Software Transactional Memory (STM) er et konsept som blir portet fra SQL-databaseverdenen - der hver operasjon utføres innenfor transaksjoner som tilfredsstiller SYRE (Atomisitet, konsistens, isolasjon, holdbarhet) eiendommer. Her, bare Atomicitet, Konsistens og Isolasjon er tilfreds fordi mekanismen kjører i minnet.

Hovedgrensesnittet i Multiverse-biblioteket er TxnObject - hvert transaksjonsobjekt trenger å implementere det, og biblioteket gir oss en rekke spesifikke underklasser vi kan bruke.

Hver operasjon som må plasseres i en kritisk seksjon, kun tilgjengelig med en tråd og bruker et hvilket som helst transaksjonsobjekt - må pakkes inn i StmUtils.atomic () metode. En kritisk seksjon er et sted i et program som ikke kan utføres av mer enn en tråd samtidig, så tilgang til det bør beskyttes av noen synkroniseringsmekanismer.

Hvis en handling i en transaksjon lykkes, vil transaksjonen bli begått, og den nye staten vil være tilgjengelig for andre tråder. Hvis det oppstår noen feil, vil ikke transaksjonen bli begått, og derfor vil staten ikke endre seg.

Til slutt, hvis to tråder vil endre den samme tilstanden i en transaksjon, er det bare en som vil lykkes og foreta endringene. Den neste tråden vil kunne utføre sin handling i løpet av transaksjonen.

4. Implementering av kontologikk ved hjelp av STM

La oss nå se på et eksempel.

La oss si at vi vil opprette en bankkontologikk ved hjelp av STM levert av Multiverse bibliotek. Våre Regnskap objektet vil ha lastUpadate tidsstempel som er av en TxnLong type, og balansere felt som lagrer nåværende saldo for en gitt konto og er av TxnInteger type.

De TxnLong og TxnInteger er klasser fra Multiverse. De må utføres innen en transaksjon. Ellers vil et unntak bli kastet. Vi må bruke StmUtils for å opprette nye forekomster av transaksjonsobjektene:

public class Account {private TxnLong lastUpdate; privat TxnInteger-balanse; offentlig konto (int-saldo) {this.lastUpdate = StmUtils.newTxnLong (System.currentTimeMillis ()); this.balance = StmUtils.newTxnInteger (balanse); }}

Deretter oppretter vi justere av () metode - som vil øke saldoen med det gitte beløpet. Denne handlingen må utføres i løpet av en transaksjon.

Hvis et unntak kastes inne i det, vil transaksjonen avsluttes uten å gjøre noen endring:

offentlig ugyldig justereBy (int-beløp) {justerBy (beløp, System.currentTimeMillis ()); } public void adjustBy (int amount, long date) {StmUtils.atomic (() -> {balance.increment (amount); lastUpdate.set (date); if (balance.get () <= 0) {kast nytt IllegalArgumentException ("Ikke nok penger"); } }); }

Hvis vi ønsker å få den nåværende saldoen for den gitte kontoen, må vi få verdien fra saldofeltet, men den må også påkalles med atomsemantikk:

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

5. Testing av kontoen

La oss teste vår Regnskap logikk. Først ønsker vi å redusere saldoen fra kontoen med det gitte beløpet ganske enkelt:

@Test offentlig ugyldig givenAccount_whenDecrement_thenShouldReturnProperValue () {Account a = new Account (10); a.adjustBy (-5); assertThat (a.getBalance ()). er EqualTo (5); }

La oss si at vi trekker oss fra kontoen og gjør saldoen negativ. Denne handlingen bør kaste et unntak, og la kontoen være intakt fordi handlingen ble utført i en transaksjon og ikke ble begått:

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

La oss nå teste et samtidighetsproblem som kan oppstå når to tråder vil redusere en balanse samtidig.

Hvis en tråd vil redusere den med 5 og den andre med 6, bør en av disse to handlingene mislykkes fordi den nåværende saldoen på den gitte kontoen er lik 10.

Vi skal sende inn to tråder til ExecutorService, og bruk CountDownLatch for å starte dem samtidig:

ExecutorService ex = Executors.newFixedThreadPool (2); Konto a = ny konto (10); CountDownLatch countDownLatch = ny CountDownLatch (1); AtomicBoolean exceptionThrown = ny AtomicBoolean (false); ex.submit (() -> {try {countDownLatch.await ();} catch (InterruptedException e) {e.printStackTrace ();} try {a.adjustBy (-6);} catch (IllegalArgumentException e) {exceptionTrowne. sett (sant);}}); ex.submit (() -> {try {countDownLatch.await ();} catch (InterruptedException e) {e.printStackTrace ();} try {a.adjustBy (-5);} catch (IllegalArgumentException e) {exceptionTrowne. sett (sant);}});

Etter å ha stirret begge handlingene samtidig, vil en av dem kaste et unntak:

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

6. Overføring fra en konto til en annen

La oss si at vi vil overføre penger fra en konto til den andre. Vi kan implementere Overfør til() metoden på Regnskap klasse ved å passere den andre Regnskap som vi ønsker å overføre den gitte mengden penger til:

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

All logikk utføres i en transaksjon. Dette vil garantere at når vi vil overføre et beløp som er høyere enn saldoen på den gitte kontoen, vil begge kontoene være intakte fordi transaksjonen ikke forplikter seg.

La oss teste overføring av logikk:

Konto a = ny konto (10); Konto b = ny konto (10); a.transferTo (b, 5); assertThat (a.getBalance ()). er EqualTo (5); assertThat (b.getBalance ()). er EqualTo (15);

Vi oppretter ganske enkelt to kontoer, vi overfører pengene fra den ene til den andre, og alt fungerer som forventet. La oss si at vi vil overføre mer penger enn det som er tilgjengelig på kontoen. De Overfør til() samtale vil kaste IllegalArgumentException, og endringene vil ikke bli forpliktet:

prøv {a.transferTo (b, 20); } fange (IllegalArgumentException e) {System.out.println ("kunne ikke overføre penger"); } assertThat (a.getBalance ()). er EqualTo (5); assertThat (b.getBalance ()). er EqualTo (15);

Merk at balansen for begge en og b kontoer er det samme som før samtalen til Overfør til() metode.

7. STM er fastlåst

Når vi bruker den vanlige Java-synkroniseringsmekanismen, kan logikken vår være utsatt for fastlåsning, uten noen måte å gjenopprette fra dem.

Låsingen kan oppstå når vi vil overføre pengene fra kontoen en til konto b. I standard Java-implementering må en tråd låse kontoen en, deretter konto b. La oss si at den andre tråden i mellomtiden vil overføre pengene fra kontoen b til konto en. Den andre tråden låser kontoen b venter på en konto en å bli låst opp.

Dessverre låsen for en konto en holdes av den første tråden, og låsen for konto b holdes av den andre tråden. En slik situasjon vil føre til at programmet vårt blokkeres på ubestemt tid.

Heldigvis når du implementerer Overfør til() logikk ved hjelp av STM, trenger vi ikke å bekymre deg for fastlåsning da STM er Deadlock Safe. La oss teste det ved å bruke vår Overfør til() metode.

La oss si at vi har to tråder. Første tråd ønsker å overføre penger fra kontoen en til konto b, og den andre tråden ønsker å overføre penger fra kontoen b til konto en. Vi må opprette to kontoer og starte to tråder som skal utføre 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 (() -> {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);});

Etter at behandlingen har startet, vil begge kontoene ha riktig balansefelt:

countDownLatch.countDown (); ex.awaitTermination (1, TimeUnit.SECONDS); ex.shutdown (); assertThat (a.getBalance ()). er EqualTo (1); assertThat (b.getBalance ()). er EqualTo (19);

8. Konklusjon

I denne opplæringen så vi på Multiverse biblioteket og hvordan vi kan bruke det til å lage låsefri og trådsikker logikk ved å bruke konsepter i Software Transactional Memory.

Vi testet oppførselen til den implementerte logikken og så at logikken som bruker STM er fastlåst.

Implementeringen av alle disse eksemplene og kodebitene finnes i GitHub-prosjektet - dette er et Maven-prosjekt, så det skal være enkelt å importere og kjøre som det er.


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