From 076727df8f76f3f0142c09507a877daba75f6917 Mon Sep 17 00:00:00 2001 From: Haroon Khan Date: Fri, 19 Nov 2021 20:51:24 +0000 Subject: [PATCH] [JAVA-8335] Fix intermittent unit test failure --- .../java/com/baeldung/abaproblem/Account.java | 17 ++++---- .../baeldung/abaproblem/AccountUnitTest.java | 40 +++++++++++-------- 2 files changed, 30 insertions(+), 27 deletions(-) diff --git a/core-java-modules/core-java-concurrency-advanced-3/src/main/java/com/baeldung/abaproblem/Account.java b/core-java-modules/core-java-concurrency-advanced-3/src/main/java/com/baeldung/abaproblem/Account.java index ee1bdcd55b..2af3113549 100644 --- a/core-java-modules/core-java-concurrency-advanced-3/src/main/java/com/baeldung/abaproblem/Account.java +++ b/core-java-modules/core-java-concurrency-advanced-3/src/main/java/com/baeldung/abaproblem/Account.java @@ -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 currentThreadCASFailureCount; + private final AtomicInteger balance; + private final AtomicInteger transactionCount; + private final ThreadLocal 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); } } diff --git a/core-java-modules/core-java-concurrency-advanced-3/src/test/java/com/baeldung/abaproblem/AccountUnitTest.java b/core-java-modules/core-java-concurrency-advanced-3/src/test/java/com/baeldung/abaproblem/AccountUnitTest.java index aa5f0f7997..3e188d682e 100644 --- a/core-java-modules/core-java-concurrency-advanced-3/src/test/java/com/baeldung/abaproblem/AccountUnitTest.java +++ b/core-java-modules/core-java-concurrency-advanced-3/src/test/java/com/baeldung/abaproblem/AccountUnitTest.java @@ -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() + ); + } }