ReviseAlgo Logo

Multithreading

Concurrency Best Practices

Write thread-safe code using immutable classes, thread-safe collections, and minimizing lock scope.

Interview: Commonly tested via design reviews: evaluate a block of code, identify concurrency bugs, and apply thread-safety optimizations.

Last Updated: June 13, 2026 12 min read

Writing robust concurrent applications requires adopting defensive programming techniques. Immutable data, minimizing lock scopes, and preferring modern concurrency collections are the foundations of thread safety.

Core Idea

Thread safety is achieved by eliminating shared mutable state (immutability) or protecting it using concurrency utilities.

Why It Matters

Adhering to best practices prevents hard-to-reproduce, non-deterministic race conditions and visibility bugs.

Interview Lens

Expect code reviews where you must analyze thread-safety bugs and refactor code using ConcurrentHashMap or Immutable classes.

Key Concurrency Principles

  • Immutability: Immutable objects (like Java Record classes or final-field classes) have no mutable state. They are inherently thread-safe and can be shared freely without locks.
  • Concurrent Collections: Avoid wrapping legacy collections in Collections.synchronizedMap() (which locks the entire collection). Use ConcurrentHashMap (which locks on bucket segments, enabling parallel reads/writes) and CopyOnWriteArrayList (safe for read-heavy operations).
  • Minimize Lock Scope: Never hold a lock during expensive operations like network requests, database transactions, or file I/O to prevent system bottlenecks.
  • Prefer JUC Utilities: Use java.util.concurrent tools (like thread pools, locks, atomics) instead of low-level wait()/notify().

Code Walkthrough

This class demonstrates implementing an immutable, thread-safe configuration holder.

public final class SystemConfig {
    private final String dbUrl;
    private final int maxConnections;

public SystemConfig(String dbUrl, int maxConnections) { this.dbUrl = dbUrl; this.maxConnections = maxConnections; }

// No setters! Only getters return copy or primitive. public String getDbUrl() { return dbUrl; } public int getMaxConnections() { return maxConnections; } }

Interview-Relevant Information

Q: Why is ConcurrentHashMap more performant than a synchronized HashMap wrapper?
Answer: Collections.synchronizedMap locks the entire map on every read or write, forcing all threads to execute sequentially. ConcurrentHashMap uses segment-level locking (bucket-level lock stripping). Threads modifying different sections of the map can execute in parallel, and reads do not require locking.

Quick Checklist

How do you make a class immutable? Why should you avoid synchronized wrappers for high concurrency maps? If yes, you understand concurrency best practices.

Use Cases

Designing thread-safe singleton state engines in high-traffic APIs.

Safely managing concurrent user connection caches.

Common Mistakes

Holding locks during network/external service calls, which severely degrades system performance.

Performing non-atomic check-then-act sequences on ConcurrentHashMap (e.g. checking if a key exists before adding, which is a race condition; use putIfAbsent instead).