[JAVA-8335] Fix intermittent unit test failure
This commit is contained in:
parent
ee70035562
commit
076727df8f
|
@ -3,17 +3,18 @@ package com.baeldung.abaproblem;
|
|||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static com.google.common.util.concurrent.Uninterruptibles.sleepUninterruptibly;
|
||||
|
||||
public class Account {
|
||||
|
||||
private AtomicInteger balance;
|
||||
private AtomicInteger transactionCount;
|
||||
private ThreadLocal<Integer> currentThreadCASFailureCount;
|
||||
private final AtomicInteger balance;
|
||||
private final AtomicInteger transactionCount;
|
||||
private final ThreadLocal<Integer> currentThreadCASFailureCount;
|
||||
|
||||
public Account() {
|
||||
this.balance = new AtomicInteger(0);
|
||||
this.transactionCount = new AtomicInteger(0);
|
||||
this.currentThreadCASFailureCount = new ThreadLocal<>();
|
||||
this.currentThreadCASFailureCount.set(0);
|
||||
this.currentThreadCASFailureCount = ThreadLocal.withInitial(() -> 0);
|
||||
}
|
||||
|
||||
public int getBalance() {
|
||||
|
@ -43,11 +44,7 @@ public class Account {
|
|||
|
||||
private void maybeWait() {
|
||||
if ("thread1".equals(Thread.currentThread().getName())) {
|
||||
try {
|
||||
TimeUnit.SECONDS.sleep(2);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
sleepUninterruptibly(2, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,13 @@
|
|||
package com.baeldung.abaproblem;
|
||||
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
@ -30,45 +35,39 @@ public class AccountUnitTest {
|
|||
assertTrue(account.deposit(moneyToDeposit));
|
||||
|
||||
assertEquals(moneyToDeposit, account.getBalance());
|
||||
assertEquals(1, account.getTransactionCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withdrawTest() throws InterruptedException {
|
||||
public void withdrawTest() {
|
||||
final int defaultBalance = 50;
|
||||
final int moneyToWithdraw = 20;
|
||||
|
||||
account.deposit(defaultBalance);
|
||||
|
||||
assertTrue(account.withdraw(moneyToWithdraw));
|
||||
|
||||
assertEquals(defaultBalance - moneyToWithdraw, account.getBalance());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void abaProblemTest() throws InterruptedException {
|
||||
public void abaProblemTest() throws Exception {
|
||||
final int defaultBalance = 50;
|
||||
|
||||
final int amountToWithdrawByThread1 = 20;
|
||||
final int amountToWithdrawByThread2 = 10;
|
||||
final int amountToDepositByThread2 = 10;
|
||||
|
||||
assertEquals(0, account.getTransactionCount());
|
||||
assertEquals(0, account.getCurrentThreadCASFailureCount());
|
||||
account.deposit(defaultBalance);
|
||||
assertEquals(1, account.getTransactionCount());
|
||||
|
||||
Thread thread1 = new Thread(() -> {
|
||||
|
||||
Runnable thread1 = () -> {
|
||||
// this will take longer due to the name of the thread
|
||||
assertTrue(account.withdraw(amountToWithdrawByThread1));
|
||||
|
||||
// thread 1 fails to capture ABA problem
|
||||
assertNotEquals(1, account.getCurrentThreadCASFailureCount());
|
||||
};
|
||||
|
||||
}, "thread1");
|
||||
|
||||
Thread thread2 = new Thread(() -> {
|
||||
|
||||
Runnable thread2 = () -> {
|
||||
assertTrue(account.deposit(amountToDepositByThread2));
|
||||
assertEquals(defaultBalance + amountToDepositByThread2, account.getBalance());
|
||||
|
||||
|
@ -79,12 +78,13 @@ public class AccountUnitTest {
|
|||
assertEquals(defaultBalance, account.getBalance());
|
||||
|
||||
assertEquals(0, account.getCurrentThreadCASFailureCount());
|
||||
}, "thread2");
|
||||
};
|
||||
|
||||
thread1.start();
|
||||
thread2.start();
|
||||
thread1.join();
|
||||
thread2.join();
|
||||
Future<?> future1 = getSingleThreadExecutorService("thread1").submit(thread1);
|
||||
Future<?> future2 = getSingleThreadExecutorService("thread2").submit(thread2);
|
||||
|
||||
future1.get();
|
||||
future2.get();
|
||||
|
||||
// compareAndSet operation succeeds for thread 1
|
||||
assertEquals(defaultBalance - amountToWithdrawByThread1, account.getBalance());
|
||||
|
@ -95,4 +95,10 @@ public class AccountUnitTest {
|
|||
// thread 2 did two modifications as well
|
||||
assertEquals(4, account.getTransactionCount());
|
||||
}
|
||||
|
||||
private static ExecutorService getSingleThreadExecutorService(String threadName) {
|
||||
return Executors.newSingleThreadExecutor(
|
||||
new ThreadFactoryBuilder().setNameFormat(threadName).build()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue