1. The Hook (The "Byte-Sized" Intro)
- In a Nutshell: Thread safety = immutability (safest), synchronization, concurrent collections, atomic variables.
- Avoid deadlocks: Lock ordering, minimize lock scope.
- Memory: Use try-with-resources (auto-close), nullify references when done, watch ThreadLocal (can leak!). Immutability = thread-safe by default (final fields, no setters). Executor framework > manual threads.
- Rule: Make thread-safe OR document "not thread-safe"!
Think of shared kitchen. Thread safety = no conflicts when multiple cooks. Immutability = read-only recipe (can't change). Deadlock = two cooks waiting for each other's utensil. Resource leaks = leaving oven on. ThreadLocal = each cook's personal cutting board!
2. Conceptual Clarity (The "Simple" Tier)
💡 The Analogy
- Thread Safety: Traffic lights (prevent collisions)
- Immutability: Frozen food (can't change)
- Deadlock: Four-way standoff (nobody moves)
- Resource Leak: Water tap left running
3. Technical Mastery (The "Deep Dive")
java
// ===========================================
// 1. THREAD SAFETY
// ===========================================
// ❌ NOT thread-safe
class Counter {
private int count = 0;
void increment() {
count++; // ❌ Race condition! (read-modify-write)
}
}
// Two threads can both read count=0, increment to 1, write back 1
// Result: count=1 instead of 2!
// ✅ SAFE: Synchronized
class Counter {
private int count = 0;
synchronized void increment() {
count++; // ✅ Atomic operation
}
}
// ✅ SAFE: AtomicInteger (better performance)
import java.util.concurrent.atomic.AtomicInteger;
class Counter {
private AtomicInteger count = new AtomicInteger(0);
void increment() {
count.incrementAndGet(); // ✅ Lock-free, atomic
}
}
// ✅ SAFEST: Immutability
final class ImmutableCounter {
private final int count; // final = never changes
ImmutableCounter(int count) {
this.count = count;
}
ImmutableCounter increment() {
return new ImmutableCounter(count + 1); // Return new object
}
}
// Thread-safe by default!
// ===========================================
// 2. CONCURRENT COLLECTIONS
// ===========================================
// ❌ NOT thread-safe
Map<String, Integer> map = new HashMap<>();
// Multiple threads modifying = corruption!
// ✅ SAFE: ConcurrentHashMap
Map<String, Integer> map = new ConcurrentHashMap<>();
map.put("key", 1); // ✅ Thread-safe
// ✅ SAFE: CopyOnWriteArrayList (reads >> writes)
List<String> list = new CopyOnWriteArrayList<>();
list.add("item"); // ✅ Thread-safe
// ❌ BAD: Synchronized wrapper (slow)
List<String> list = Collections.synchronizedList(new ArrayList<>());
// Locks entire list for every operation!
// ===========================================
// 3. AVOIDING DEADLOCKS
// ===========================================
// ❌ DEADLOCK: Inconsistent lock ordering
class BankAccount {
private double balance;
synchronized void transferTo(BankAccount target, double amount) {
synchronized (target) { // ❌ Inconsistent lock order!
this.balance -= amount;
target.balance += amount;
}
}
}
// Thread 1: A.transferTo(B) → locks A, waits for B
// Thread 2: B.transferTo(A) → locks B, waits for A
// DEADLOCK!
// ✅ FIX: Consistent lock ordering
synchronized void transferTo(BankAccount target, double amount) {
BankAccount first = this.id < target.id ? this : target;
BankAccount second = this.id < target.id ? target : this;
synchronized (first) {
synchronized (second) {
this.balance -= amount;
target.balance += amount;
}
}
}
// Always lock in same order → no deadlock!
// ===========================================
// 4. EXECUTOR FRAMEWORK
// ===========================================
// ❌ BAD: Manual thread creation
for (int i = 0; i < 1000; i++) {
new Thread(() -> processTask()).start(); // ❌ Creates 1000 threads!
}
// ✅ GOOD: Thread pool
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 1000; i++) {
executor.submit(() -> processTask()); // ✅ Reuses 10 threads
}
executor.shutdown();
executor.awaitTermination(1, TimeUnit.MINUTES);
// ===========================================
// 5. RESOURCE MANAGEMENT
// ===========================================
// ❌ BAD: Manual close (can leak if exception!)
FileInputStream fis = new FileInputStream("file.txt");
try {
// Use fis
} finally {
fis.close(); // ❌ Might not be called if exception!
}
// ✅ GOOD: try-with-resources
try (FileInputStream fis = new FileInputStream("file.txt")) {
// Use fis
} // ✅ Auto-closed, even if exception!
// Multiple resources
try (Connection conn = getConnection();
PreparedStatement stmt = conn.prepareStatement(SQL);
ResultSet rs = stmt.executeQuery()) {
// Use resources
} // ✅ All auto-closed in reverse order!
// ===========================================
// 6. MEMORY LEAKS (Java CAN have leaks!)
// ===========================================
// ❌ LEAK: Static collection never cleared
class UserCache {
private static Map<Integer, User> cache = new HashMap<>();
void addUser(User user) {
cache.put(user.getId(), user); // ❌ Never removed!
}
}
// Grows forever → OutOfMemoryError!
// ✅ FIX: Use WeakHashMap or clear old entries
class UserCache {
private static Map<Integer, User> cache = new WeakHashMap<>();
// Entries auto-removed when User no longer referenced elsewhere
}
// ❌ LEAK: ThreadLocal not cleaned up
class RequestContext {
private static ThreadLocal<User> currentUser = new ThreadLocal<>();
static void setUser(User user) {
currentUser.set(user); // ❌ Never removed!
}
}
// In thread pools, threads are reused → leak!
// ✅ FIX: Always remove
try {
RequestContext.setUser(user);
// Process request
} finally {
RequestContext.remove(); // ✅ Clean up!
}
// ===========================================
// 7. IMMUTABILITY FOR THREAD SAFETY
// ===========================================
// ✅ Immutable class (thread-safe)
final class Money {
private final BigDecimal amount;
private final String currency;
Money(BigDecimal amount, String currency) {
this.amount = amount;
this.currency = currency;
}
// No setters!
Money add(Money other) {
if (!currency.equals(other.currency)) {
throw new IllegalArgumentException("Currency mismatch");
}
return new Money(amount.add(other.amount), currency);
}
}
// Can be safely shared between threads!5. The Comparison & Decision Layer
| Scenario | Solution |
|---|---|
| Simple counter | AtomicInteger |
| Complex state | synchronized or Lock |
| Collection | ConcurrentHashMap, CopyOnWriteArrayList |
| Task execution | ExecutorService (not new Thread()) |
| Resource management | try-with-resources |
6. The "Interview Corner" (The Edge)
The "Killer" Interview Question: "What causes deadlock and how to prevent?" Answer: Deadlock = circular wait (A waits for B, B waits for A)!
Cause:
java
// Thread 1:
synchronized(A) {
synchronized(B) { /* ... */ }
}
// Thread 2:
synchronized(B) {
synchronized(A) { /* ... */ } // ❌ Different order!
}
// DEADLOCK!Prevention:
- Lock ordering: Always acquire locks in same order
- Timeout: tryLock(timeout)
- Minimize scope: Hold locks for shortest time
- Avoid nested locks: Use higher-level constructs
Pro-Tips:
- Prefer immutability:
java
// Mutable (needs synchronization)
class Counter {
private int count;
synchronized void increment() { count++; }
}
// Immutable (no synchronization needed!)
final class Counter {
private final int count;
Counter increment() { return new Counter(count + 1); }
}- ThreadLocal cleanup (critical in web apps):
java
// Filter in web app
try {
ThreadContext.set(requestData);
chain.doFilter(request, response);
} finally {
ThreadContext.remove(); // ✅ Always clean up!
}