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
This commit is contained in:
parent
ee79ac3a02
commit
ce43d80f8b
|
@ -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>
|
||||
|
@ -236,7 +236,6 @@
|
|||
<artifactId>datanucleus-xml</artifactId>
|
||||
<version>5.0.0-release</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-web</artifactId>
|
||||
|
@ -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>
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
Loading…
Reference in New Issue