π― 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:
synchronizedensures 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