π¦ Bank Transaction Concurrency Problem

Table of contents

Problem Statement
Multiple threads (ATM users, online banking) access shared bank accounts.
Operations:
deposit(amount)
withdraw(amount)
getBalance()
Requirements:
Thread-safe: No race conditions during concurrent transactions.
Consistency: Balance never goes negative (unless overdraft allowed).
Deadlock-free: No circular waits between accounts (e.g., transfer between A and B).
π‘ Solution Approaches
1. Basic Thread-Safe Account (Synchronized Methods)
To make the BankAccount
thread-safe, we start by marking the deposit
, withdraw
, and getBalance
methods as synchronized
so only one thread can access them at a time per object.
Key Mechanics
Every Java object has an intrinsic lock (monitor).
synchronized
methods acquire this lock before execution.Only one thread can hold the lock at a time β all other threads block.
deposit()
and withdraw()
at the same time on the same object, so balance
remains consistent.- π‘β However, if two threads operate on different objects, synchronization won't help because each object has a different lock.
public class BankAccount {
private double balance;
public synchronized void deposit(double amount) {
balance += amount;
}
public synchronized void withdraw(double amount) throws InsufficientFundsException {
if (balance < amount) throw new InsufficientFundsException();
balance -= amount;
}
public synchronized double getBalance() {
return balance;
}
}
Case 1: Simple Deposit-Withdraw (No Conflict)
BankAccount account = new BankAccount();
// Thread 1
account.deposit(100); // β
Balance = 100
// Thread 2
account.withdraw(30); // β
Balance = 70
Case 2: Concurrent Deposits (Lost Update Problem Prevented)
Case 3: Overdraft Prevention
// Thread 1
account.withdraw(150); // β Fails (balance=100)
// Thread 2
account.getBalance(); // β
Returns 100 (consistent read)
Where It Fails (Edge Cases)
Problem 1: Nested Operations Deadlock
Consider if we are going to use this Object and tranfering money from one account to another
// Thread 1
synchronized void transfer(BankAccount to, double amount) {
this.withdraw(amount); // Already holds lock on 'this'
to.deposit(amount); // Needs lock on 'to' β DEADLOCK if another thread does reverse transfer
}
Problem 2: Liveness Issues (Performance)
All operations serialize (even
getBalance()
blocks writes).Under high contention, throughput plummets.
Problem 3: Compound Actions Not Atomic
if (account.getBalance() >= 100) { // Race condition!
account.withdraw(100); // Balance may change between check and withdraw
}
// Operation is not Atomic, may cause issue if any changes happen between two operations.
More Apporaches, Explanation and Fixes of above problem will covered in our course.
Subscribe to my newsletter
Read articles from Subhahu Jain directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
