ABA problem in concurrency
This commit is contained in:
parent
a04fb286e4
commit
a6fc46e3f6
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue