[JAVA-8335] Fix intermittent unit test failure

This commit is contained in:
Haroon Khan 2021-11-19 20:51:24 +00:00
parent ee70035562
commit 076727df8f
2 changed files with 30 additions and 27 deletions

View File

@ -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);
}
}

View File

@ -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()
);
}
}