๐ฏ 1. Setting the Scene — The Interview Moment
Imagine this…
Interviewer: “So, how would you handle multiple threads accessing a shared bank account in Java?”
Me: “You mean… like multiple ATMs punching the same account balance at the same time?” ๐ฆ๐ณ
Interviewer: smiles like a villain in a heist movie ๐ “Exactly.”
๐จ 2. Without Thread Safety — What Happens?
Bad Code (Singleton Bean or Shared Object)
class BankAccount {
private int balance = 1000; // Shared among threads
public void deposit(int amount) {
balance += amount; // ๐จ Not synchronized
System.out.println(Thread.currentThread().getName()
+ " deposited ₹" + amount + ", Balance: ₹" + balance);
}
public void withdraw(int amount) {
if (balance >= amount) {
balance -= amount; // ๐จ Not synchronized
System.out.println(Thread.currentThread().getName()
+ " withdrew ₹" + amount + ", Balance: ₹" + balance);
} else {
System.out.println(Thread.currentThread().getName()
+ " tried to withdraw ₹" + amount + " but insufficient balance!");
}
}
}
public class BankTest {
public static void main(String[] args) {
BankAccount account = new BankAccount();
Thread t1 = new Thread(() -> {
account.deposit(500);
account.withdraw(200);
}, "ATM-1");
Thread t2 = new Thread(() -> {
account.withdraw(300);
account.deposit(400);
}, "ATM-2");
t1.start();
t2.start();
}
}
๐ฅ The Problem
balance is a class-level variable shared between all threads.
Threads run in parallel, so:
- ATM-1 reads balance = 1000, adds 500 → 1500 (temporarily in its CPU cache).
- ATM-2 at the same time reads balance = 1000, subtracts 300 → 700.
- The writes overwrite each other → lost updates.
๐ผ Diagram — Without Thread Safety
Shared BankAccount object (Heap Memory)
---------------------------------------
balance = 1000
ATM-1 Thread ATM-2 Thread
----------- -----------
Reads balance=1000 Reads balance=1000
+500 = 1500 -300 = 700
Writes 1500 Writes 700
❌ 1500 lost, final balance = 700
This is called a race condition — threads racing to update the same variable.
๐ก️ 3. Making It Thread-Safe
Fix 1 — synchronized keyword
class BankAccount {
private int balance = 1000;
public synchronized void deposit(int amount) {
balance += amount;
System.out.println(Thread.currentThread().getName()
+ " deposited ₹" + amount + ", Balance: ₹" + balance);
}
public synchronized void withdraw(int amount) {
if (balance >= amount) {
balance -= amount;
System.out.println(Thread.currentThread().getName()
+ " withdrew ₹" + amount + ", Balance: ₹" + balance);
} else {
System.out.println(Thread.currentThread().getName()
+ " tried to withdraw ₹" + amount + " but insufficient balance!");
}
}
}
๐ก Why it works:
synchronized
ensures only one thread at a time executes the method on that object.- Prevents overlapping reads/writes.
๐ผ Diagram — With synchronized
Shared BankAccount object (Heap Memory)
---------------------------------------
balance = 1000
ATM-1 Thread --------------|
| Lock acquired
Updates balance safely |
| Lock released
|
ATM-2 Thread --------------|
Waits until ATM-1 finishes
Fix 2 — Database Transactions (Real World)
If this were a microservice, thread safety in code is not enough —
you’d need transaction isolation in the database so even if multiple service instances run, updates happen safely.
Fix 3 — @RequestScope or @Prototype
Works only if each request has its own account object (like a form or cart).
❌ Doesn’t work for real banking balances, because balances are shared globally.
๐ 4. Learning Takeaway
“Threads are like customers at an ATM — if you don’t control access, one will take your money while another is counting it.” ๐ฐ
Key Points:
- Class-level variables in singleton beans are shared → not thread-safe.
- Local method variables are thread-safe by default (each thread has its own stack).
- Use:
synchronized
/ Lock for in-memory protection.- Database locks for multi-instance services.
- Scopes like @RequestScope only when state is per-request, not shared.
Comments
Post a Comment