Java Multithreading and Concurrency Interview Questions: Deep Dive

Concurrency is where Java interviews separate seniors from everyone else. The questions look simple — "what does volatile do?" — but each one has a trapdoor. This deep dive gives you the precise answer plus the follow-up hiding underneath.

1. What does volatile actually guarantee? (The #1 trap)

Answer: volatile guarantees visibility (writes are immediately visible to other threads) and ordering (no instruction reordering around the access) — but NOT atomicity. This fails:

volatile int counter = 0;
counter++;   // read-modify-write: NOT atomic, even with volatile!

Use volatile for flags (volatile boolean running = true); use AtomicInteger or synchronization for read-modify-write. Saying "volatile makes it thread-safe" is the fastest way to fail the round.

2. synchronized vs ReentrantLock?

synchronized (lock) { /* ... */ }   // block-structured, auto-release

lock.lock();
try { /* ... */ } finally { lock.unlock(); }   // must release manually

Answer: synchronized is simpler and the JVM optimizes it aggressively (biased/lock elision). ReentrantLock buys you: timed/try-lock (tryLock(1, SECONDS)), interruptible lock acquisition, fairness policy, and multiple Conditions. Rule of thumb: synchronized until you need one of those four.

3. Explain deadlock — and the four conditions

// classic: opposite lock ordering
Thread A: synchronized (lock1) { synchronized (lock2) { ... } }
Thread B: synchronized (lock2) { synchronized (lock1) { ... } }

Answer: deadlock needs all four Coffman conditions: mutual exclusion, hold-and-wait, no preemption, circular wait. You break it by breaking circular wait — impose a global lock ordering (always acquire lock1 before lock2). Follow-ups: detect via jstack thread dumps or ThreadMXBean.findDeadlockedThreads(); prevent with tryLock timeouts.

4. How do you size a thread pool?

Executors.newFixedThreadPool(nThreads);          // CPU-bound: nThreads ≈ cores
Executors.newCachedThreadPool();                  // short-lived tasks (unbounded — dangerous)
Executors.newVirtualThreadPerTaskExecutor();      // Java 21+: blocking I/O at scale

Answer: CPU-bound → threads ≈ CPU cores (more threads just context-switch). I/O-bound → threads ≈ cores × (1 + wait/compute ratio). And never create threads in a loop — unbounded creation is how production falls over; a bounded pool with a rejection policy (AbortPolicy, CallerRunsPolicy) is the professional answer.

5. How does ConcurrentHashMap achieve thread safety?

Answer: lock striping — the map is divided into segments/buckets with fine-grained locking (modern JDK: CAS + synchronized on individual bins), so 16 threads writing different keys rarely contend. Reads are mostly lock-free via volatile table entries. Contrast: Collections.synchronizedMap locks the whole map per operation, and Hashtable is its legacy twin. Bonus: computeIfAbsent is atomic — the canonical cache pattern.

6. wait/notify vs Condition?

Answer: wait()/notify() work with synchronized and give you one wait-set per lock. Condition (from a Lock) gives you multiple wait-sets per lock — e.g. a bounded buffer with separate notFull and notEmpty conditions, so producers and consumers wake only the right waiters. Always call wait() in a while loop re-checking the condition (spurious wakeups are real).

7. CompletableFuture in 60 seconds

CompletableFuture.supplyAsync(() -> fetchUser(id))
    .thenCombine(CompletableFuture.supplyAsync(() -> fetchOrders(id)),
                 (user, orders) -> new Profile(user, orders))
    .exceptionally(ex -> Profile.empty())   // never leave a future hanging
    .join();

Answer: monadic async composition — thenApply (sync transform), thenCompose (async flatMap), thenCombine/allOf (joining), exceptionally (recovery). Default pool is the common ForkJoinPool — pass your own executor for I/O work so you don't starve it. The interview follow-up: "Future vs CompletableFuture?" — Future.get() blocks with no composition; CompletableFuture chains without blocking.

8. What is ThreadLocal, and what's the catch?

Answer: per-thread storage — each thread sees its own value (classic uses: SimpleDateFormat instances, request contexts). The catch: in thread pools, threads are reused, so a stale ThreadLocal leaks data across tasks (and memory, if never removed) — always remove() in a finally. In Java 21+, prefer scoped values for immutable inherited context.

Quick-fire revision table

QuestionOne-line answer
volatileVisibility + ordering, NOT atomicity
synchronized vs LockSimple vs timed/try/interruptible/fair
Deadlock fixGlobal lock ordering breaks circular wait
Pool sizingCPU-bound ≈ cores; I/O-bound ≈ cores × (1 + wait/compute)
ConcurrentHashMapFine-grained bin locking + lock-free reads
CompletableFutureNon-blocking async composition with recovery

In this series

  1. Java 17 to 21 Interview Questions: Records, Pattern Matching, Virtual Threads — records, pattern matching, virtual threads.
  2. Spring Boot Interview Questions and Answers (2026 Edition) — auto-configuration to actuators.
  3. Java Multithreading and Concurrency Interview Questions: Deep Dive (this post) — volatile, locks, deadlocks, pools.
  4. Microservices Interview Questions for Java Developers — decomposition, resilience, sagas.

More: Java Interview Prep hub — the full collection of Java interview questions on JavaMakeUse.

Comments

Popular posts from this blog

Java Banking Finance Services and Insurance (BFSI) domain interview questions

JSP Servlet Interview Questions For Freshers Series 1

Java program to check even or odd number