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"?>
|
<?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"
|
<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>
|
<parent>
|
||||||
<artifactId>parent-modules</artifactId>
|
<artifactId>parent-modules</artifactId>
|
||||||
<groupId>com.baeldung</groupId>
|
<groupId>com.baeldung</groupId>
|
||||||
@ -236,7 +236,6 @@
|
|||||||
<artifactId>datanucleus-xml</artifactId>
|
<artifactId>datanucleus-xml</artifactId>
|
||||||
<version>5.0.0-release</version>
|
<version>5.0.0-release</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework</groupId>
|
<groupId>org.springframework</groupId>
|
||||||
<artifactId>spring-web</artifactId>
|
<artifactId>spring-web</artifactId>
|
||||||
@ -254,21 +253,25 @@
|
|||||||
<version>3.0.3</version>
|
<version>3.0.3</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.zaxxer</groupId>
|
<groupId>org.multiverse</groupId>
|
||||||
<artifactId>HikariCP</artifactId>
|
<artifactId>multiverse-core</artifactId>
|
||||||
<version>2.6.1</version>
|
<version>${multiverse.version}</version>
|
||||||
<scope>compile</scope>
|
</dependency>
|
||||||
</dependency>
|
<dependency>
|
||||||
<dependency>
|
<groupId>com.zaxxer</groupId>
|
||||||
<groupId>org.postgresql</groupId>
|
<artifactId>HikariCP</artifactId>
|
||||||
<artifactId>postgresql</artifactId>
|
<version>2.6.1</version>
|
||||||
<version>42.0.0</version>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.postgresql</groupId>
|
||||||
|
<artifactId>postgresql</artifactId>
|
||||||
|
<version>42.0.0</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
|
<multiverse.version>0.7.0</multiverse.version>
|
||||||
<cglib.version>3.2.4</cglib.version>
|
<cglib.version>3.2.4</cglib.version>
|
||||||
<commons-lang.version>3.5</commons-lang.version>
|
<commons-lang.version>3.5</commons-lang.version>
|
||||||
<jasypt.version>1.9.2</jasypt.version>
|
<jasypt.version>1.9.2</jasypt.version>
|
||||||
|
50
libraries/src/main/java/com/baeldung/stm/Account.java
Normal file
50
libraries/src/main/java/com/baeldung/stm/Account.java
Normal file
@ -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
libraries/src/test/java/com/baeldung/stm/AccountTest.java
Normal file
145
libraries/src/test/java/com/baeldung/stm/AccountTest.java
Normal file
@ -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…
x
Reference in New Issue
Block a user