Skip to content

Commit

Permalink
BAEL-855 stm (eugenp#1855)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
tomekl007 authored and pedja4 committed May 16, 2017
1 parent ee79ac3 commit ce43d80
Show file tree
Hide file tree
Showing 3 changed files with 213 additions and 15 deletions.
33 changes: 18 additions & 15 deletions libraries/pom.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>parent-modules</artifactId>
<groupId>com.baeldung</groupId>
Expand Down Expand Up @@ -236,7 +236,6 @@
<artifactId>datanucleus-xml</artifactId>
<version>5.0.0-release</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
Expand All @@ -254,21 +253,25 @@
<version>3.0.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>2.6.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.0.0</version>
</dependency>

<dependency>
<groupId>org.multiverse</groupId>
<artifactId>multiverse-core</artifactId>
<version>${multiverse.version}</version>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>2.6.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.0.0</version>
</dependency>
</dependencies>

<properties>
<multiverse.version>0.7.0</multiverse.version>
<cglib.version>3.2.4</cglib.version>
<commons-lang.version>3.5</commons-lang.version>
<jasypt.version>1.9.2</jasypt.version>
Expand Down
50 changes: 50 additions & 0 deletions libraries/src/main/java/com/baeldung/stm/Account.java
Original file line number Diff line number Diff line change
@@ -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<String>)
txn -> "Balance: " + balance.get(txn) + " lastUpdateDate: " + lastUpdate.get(txn));
}
}
145 changes: 145 additions & 0 deletions libraries/src/test/java/com/baeldung/stm/AccountTest.java
Original file line number Diff line number Diff line change
@@ -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);
}


}

0 comments on commit ce43d80

Please sign in to comment.