🏦 Bank Transaction Concurrency Problem

Subhahu JainSubhahu Jain
2 min read

Problem Statement

  • Multiple threads (ATM users, online banking) access shared bank accounts.

  • Operations:

    • deposit(amount)

    • withdraw(amount)

    • getBalance()

  • Requirements:

    1. Thread-safe: No race conditions during concurrent transactions.

    2. Consistency: Balance never goes negative (unless overdraft allowed).

    3. 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.

πŸ’‘
In Synchronized Method: Two threads can’t execute 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.

0
Subscribe to my newsletter

Read articles from Subhahu Jain directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Subhahu Jain
Subhahu Jain