Race Conditions & Concurrency
“If two correct actions happen at the same time and the result is wrong — you have a race condition.”
Today we move from duplicates (Day 2) to simultaneous execution, which is the #1 cause of “works in test, breaks in production”.
1️⃣ What Is a Race Condition (Plain English)
A race condition happens when:
- Two or more executions run at the same time
- They read shared data
- They update based on stale values
Whoever “wins the race” overwrites the other.
2️⃣ The Classic Banking Example
Initial state:
Balance = 100
Two users at the same time:
Thread A reads balance → 100
Thread B reads balance → 100
Thread A deducts 70 → writes 30
Thread B deducts 40 → writes 60 ❌ WRONG
Both were “correct”. Final result is corrupted.
3️⃣ Why You Rarely See This in Development
- Local machine → 1 user
- Low latency
- No retries
- No real concurrency
Production:
- Hundreds of threads
- Network delays
- Multiple instances
? Concurrency bugs hide until load.
4️⃣ Golden Rule of Concurrency (MEMORIZE)
Read → Check → Write must be atomic
If those 3 steps are not protected, corruption is guaranteed.
5️⃣ Where Race Conditions Occur
| Layer | Example |
|---|---|
| Database | Lost updates |
| Application | Shared variables |
| Cache | Stale values |
| Microservices | Double processing |
| Message queues | Redelivery |
6️⃣ Database-Level Protection (MOST IMPORTANT)
? Pessimistic Locking (Strongest)
SELECT * FROM account WHERE id = 1 FOR UPDATE;
What it does:
- Locks the row
- Other transactions wait
- No overwrite possible
Used by:
- Banks
- Inventory systems
- Seat booking
⚡ Optimistic Locking (Scalable)
@Version
private Integer version;
If version changed → update fails.
Used when:
- Reads are high
- Writes are rare
7️⃣ Application-Level Mistakes ❌
if (balance >= amount) {
balance -= amount;
save(balance);
}
❌ This is NOT safe
Because read & write are separate.
8️⃣ Correct Safe Pattern ✅
Database-enforced update
UPDATE account
SET balance = balance - 70
WHERE id = 1 AND balance >= 70;
If rows updated = 0 → fail safely.
9️⃣ Spring Boot Safe Pattern
@Transactional
public void withdraw(Long id, int amount) {
Account acc = repo.findForUpdate(id); // LOCK
if (acc.getBalance() < amount)
throw new InsufficientFundsException();
acc.setBalance(acc.getBalance() - amount);
}
? Lock Ordering (Deadlock Prevention)
Always lock in same order:
Account A → Account B
Never:
A → B
B → A
11️⃣ Why synchronized Is Useless Here ❌
- Works only in one JVM
- Fails in clustered apps
- DB remains unprotected
Concurrency safety must live in DB, not Java.
12️⃣ Mental Checklist for Every Write Method
Ask yourself:
- Can two threads run this at same time?
- Do they read shared data?
- Is read–check–write atomic?
- Is DB enforcing correctness?
If any answer is “no” → bug waiting.
? Mini Exercise
Take any update query and ask:
- What happens if two users execute this simultaneously?
- Who protects the data — code or DB?
? Day 4 Preview
Deadlocks — When Safety Itself Causes Failure
You’ll learn:
- Why deadlocks happen
- How to detect them
- How banks avoid them