From ce43d80f8b83accdee67047f33a906b7a893ae76 Mon Sep 17 00:00:00 2001 From: Tomasz Lelek Date: Tue, 16 May 2017 16:53:47 +0200 Subject: [PATCH] BAEL-855 stm (#1855) * BAEL-855 code for the STM article * BAEL-855 method ordering * BAEL-855 Better test case * BAEL-855 formatting * BAEL-855 rename * BAEL-855 change to expected * Merge branch 'master' of https://github.com/eugenp/tutorials into BAEL-855_stm # Conflicts: # libraries/pom.xml --- libraries/pom.xml | 33 ++-- .../main/java/com/baeldung/stm/Account.java | 50 ++++++ .../java/com/baeldung/stm/AccountTest.java | 145 ++++++++++++++++++ 3 files changed, 213 insertions(+), 15 deletions(-) create mode 100644 libraries/src/main/java/com/baeldung/stm/Account.java create mode 100644 libraries/src/test/java/com/baeldung/stm/AccountTest.java diff --git a/libraries/pom.xml b/libraries/pom.xml index bf50e699b8..1ad1ff7d8e 100644 --- a/libraries/pom.xml +++ b/libraries/pom.xml @@ -1,6 +1,6 @@ + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> parent-modules com.baeldung @@ -236,7 +236,6 @@ datanucleus-xml 5.0.0-release - org.springframework spring-web @@ -254,21 +253,25 @@ 3.0.3 test - - com.zaxxer - HikariCP - 2.6.1 - compile - - - org.postgresql - postgresql - 42.0.0 - - + + org.multiverse + multiverse-core + ${multiverse.version} + + + com.zaxxer + HikariCP + 2.6.1 + compile + + + org.postgresql + postgresql + 42.0.0 + - + 0.7.0 3.2.4 3.5 1.9.2 diff --git a/libraries/src/main/java/com/baeldung/stm/Account.java b/libraries/src/main/java/com/baeldung/stm/Account.java new file mode 100644 index 0000000000..734d332308 --- /dev/null +++ b/libraries/src/main/java/com/baeldung/stm/Account.java @@ -0,0 +1,50 @@ +package com.baeldung.stm; + +import org.multiverse.api.StmUtils; +import org.multiverse.api.callables.TxnCallable; +import org.multiverse.api.references.TxnInteger; +import org.multiverse.api.references.TxnLong; + +public class Account { + + private final TxnLong lastUpdate; + private final TxnInteger balance; + + public Account(final int balance) { + this.lastUpdate = StmUtils.newTxnLong(System.currentTimeMillis()); + this.balance = StmUtils.newTxnInteger(balance); + } + + public Integer getBalance() { + return balance.atomicGet(); + } + + public void adjustBy(final int amount) { + adjustBy(amount, System.currentTimeMillis()); + } + + public void adjustBy(final int amount, final long date) { + StmUtils.atomic(() -> { + balance.increment(amount); + lastUpdate.set(date); + + if (balance.get() < 0) { + throw new IllegalArgumentException("Not enough money"); + } + }); + } + + public void transferTo(final Account other, final int amount) { + StmUtils.atomic(() -> { + final long date = System.currentTimeMillis(); + adjustBy(-amount, date); + other.adjustBy(amount, date); + }); + } + + @Override + public String toString() { + return StmUtils.atomic((TxnCallable) + txn -> "Balance: " + balance.get(txn) + " lastUpdateDate: " + lastUpdate.get(txn)); + } +} \ No newline at end of file diff --git a/libraries/src/test/java/com/baeldung/stm/AccountTest.java b/libraries/src/test/java/com/baeldung/stm/AccountTest.java new file mode 100644 index 0000000000..d45490df94 --- /dev/null +++ b/libraries/src/test/java/com/baeldung/stm/AccountTest.java @@ -0,0 +1,145 @@ +package com.baeldung.stm; + + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import static org.assertj.core.api.Java6Assertions.assertThat; +import static org.junit.Assert.assertTrue; + +public class AccountTest { + + @Test + public void givenAccount_whenDecrement_thenShouldReturnProperValue() { + //given + Account a = new Account(10); + + //when + a.adjustBy(-5); + + //then + assertThat(a.getBalance()).isEqualTo(5); + } + + @Test(expected = IllegalArgumentException.class) + public void givenAccount_whenDecrementTooMuch_thenShouldThrow() { + //given + Account a = new Account(10); + + //when + a.adjustBy(-11); + } + + @Test + public void givenTwoThreads_whenBothApplyOperation_thenShouldThrow() throws InterruptedException { + //given + ExecutorService ex = Executors.newFixedThreadPool(2); + Account a = new Account(10); + CountDownLatch countDownLatch = new CountDownLatch(1); + AtomicBoolean exceptionThrown = new AtomicBoolean(false); + + //when + 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); + } + }); + + countDownLatch.countDown(); + ex.awaitTermination(1, TimeUnit.SECONDS); + ex.shutdown(); + + //then + assertTrue(exceptionThrown.get()); + } + + @Test + public void givenTwoAccounts_whenFailedWhileTransferring_thenShouldRollbackTransaction() { + //given + final Account a = new Account(10); + final Account b = new Account(10); + + //when + a.transferTo(b, 5); + + //then + assertThat(a.getBalance()).isEqualTo(5); + assertThat(b.getBalance()).isEqualTo(15); + + //and + try { + a.transferTo(b, 20); + } catch (final IllegalArgumentException e) { + System.out.println("failed to transfer money"); + } + + //then + assertThat(a.getBalance()).isEqualTo(5); + assertThat(b.getBalance()).isEqualTo(15); + } + + @Test + public void givenTwoThreads_whenBothTryToTransfer_thenShouldNotDeadlock() throws InterruptedException { + //given + ExecutorService ex = Executors.newFixedThreadPool(2); + final Account a = new Account(10); + final Account b = new Account(10); + CountDownLatch countDownLatch = new CountDownLatch(1); + + //when + 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); + + }); + + countDownLatch.countDown(); + ex.awaitTermination(1, TimeUnit.SECONDS); + ex.shutdown(); + + //then + assertThat(a.getBalance()).isEqualTo(1); + assertThat(b.getBalance()).isEqualTo(19); + } + + +} \ No newline at end of file