From a6fc46e3f6ed643215ae21ca28d22b095c4a839c Mon Sep 17 00:00:00 2001 From: Gergo Petrik Date: Mon, 4 May 2020 16:40:20 +0200 Subject: [PATCH] ABA problem in concurrency --- .../java/com/baeldung/abaproblem/Account.java | 34 +++++++ .../baeldung/abaproblem/AccountUnitTest.java | 93 +++++++++++++++++++ 2 files changed, 127 insertions(+) create mode 100644 core-java-modules/core-java-concurrency-advanced-3/src/main/java/com/baeldung/abaproblem/Account.java create mode 100644 core-java-modules/core-java-concurrency-advanced-3/src/test/java/com/baeldung/abaproblem/AccountUnitTest.java 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 new file mode 100644 index 0000000000..0204c31fea --- /dev/null +++ b/core-java-modules/core-java-concurrency-advanced-3/src/main/java/com/baeldung/abaproblem/Account.java @@ -0,0 +1,34 @@ +package com.baeldung.abaproblem; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +public class Account { + + private AtomicInteger balance = new AtomicInteger(0); + + public int getBalance() { + return balance.get(); + } + + public boolean withdraw(int amount) throws InterruptedException { + int current = getBalance(); + if (current < amount) { + throw new RuntimeException("Not sufficient balance"); + } + precessBalance(); + return balance.compareAndSet(current, current - amount); + } + + private void precessBalance() throws InterruptedException { + if ("thread 1".equals(Thread.currentThread().getName())) { + TimeUnit.SECONDS.sleep(2); + } + } + + public boolean deposit(int amount) { + int current = balance.get(); + return balance.compareAndSet(current, current + amount); + } + +} 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 new file mode 100644 index 0000000000..ab88a0f447 --- /dev/null +++ b/core-java-modules/core-java-concurrency-advanced-3/src/test/java/com/baeldung/abaproblem/AccountUnitTest.java @@ -0,0 +1,93 @@ +package com.baeldung.abaproblem; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class AccountUnitTest { + + private Account account; + + @BeforeEach + public void setUp() { + account = new Account(); + } + + @Test + public void zeroBalanceInitializationTest() { + assertEquals(0, account.getBalance()); + } + + @Test + public void depositTest() { + final int moneyToDeposit = 50; + + assertTrue(account.deposit(moneyToDeposit)); + + assertEquals(moneyToDeposit, account.getBalance()); + } + + @Test + public void withdrawTest() throws InterruptedException { + final int defaultBalance = 50; + final int moneyToWithdraw = 20; + + account.deposit(defaultBalance); + + assertTrue(account.withdraw(moneyToWithdraw)); + + assertEquals(defaultBalance - moneyToWithdraw, account.getBalance()); + } + + @Test + public void withdrawWithoutSufficientBalanceTest() { + assertThrows(RuntimeException.class, () -> account.withdraw(10)); + } + + @Test + public void abaProblemTest() throws InterruptedException { + final int defaultBalance = 50; + + final int amountToWithdrawByThreadA = 20; + final int amountToWithdrawByThreadB = 10; + final int amountToDepositByThreadB = 10; + + account.deposit(defaultBalance); + + Thread threadA = new Thread(() -> { + try { + // this will take longer due to the name of the thread + assertTrue(account.withdraw(amountToWithdrawByThreadA)); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + }, "tread 1"); + + Thread threadB = new Thread(() -> { + + assertTrue(account.deposit(amountToDepositByThreadB)); + assertEquals(defaultBalance + amountToDepositByThreadB, account.getBalance()); + try { + // this will be fast due to the name of the thread + assertTrue(account.withdraw(amountToWithdrawByThreadB)); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + + // thread 1 didn't finish yet, so the original value will be in place for it + assertEquals(defaultBalance, account.getBalance()); + + }, "thread 2"); + + threadA.start(); + threadB.start(); + threadA.join(); + threadB.join(); + + // compareAndSet operation succeeds for thread 1 + assertEquals(defaultBalance - amountToWithdrawByThreadA, account.getBalance()); + } +}