Lesson Completion
Back to course

Concurrency and Memory Best Practices: Thread Safety and Resource Management

Beginner
12 minutes4.9Java

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

ScenarioSolution
Simple counterAtomicInteger
Complex statesynchronized or Lock
CollectionConcurrentHashMap, CopyOnWriteArrayList
Task executionExecutorService (not new Thread())
Resource managementtry-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:

  1. Lock ordering: Always acquire locks in same order
  2. Timeout: tryLock(timeout)
  3. Minimize scope: Hold locks for shortest time
  4. Avoid nested locks: Use higher-level constructs

Pro-Tips:

  1. 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); } }
  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! }

Topics Covered

Java Fundamentals

Tags

#java#programming#beginner-friendly

Last Updated

2025-02-01