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

In this series

  1. 50 Most Asked Java Interview Questions in 2026 — Core Java, Java 25, Spring Boot & Microservices — start here.
  2. Top 40 Java Interview Questions and Answers (2026 Edition) — the 40-question companion set.
  3. Java 17 to 21 Interview Questions: Records, Pattern Matching, Sealed Classes & More — modern language deep dive.
  4. Spring Boot Interview Questions and Answers — DI, transactions, JPA.
  5. Java Multithreading and Concurrency Interview Questions (this post) — threads, locks, virtual threads, JVM.
  6. Microservices Interview Questions for Java Developers — resilience and distributed patterns.

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

Comments

Popular posts from this blog

JSP Servlet Interview Questions For Freshers Series 1

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

Java program to check even or odd number

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