Why interviewers ask about multithreading
Almost every backend system Java runs is concurrent — request threads, background jobs, connection pools — so your grasp of shared mutable state predicts whether your code will corrupt data under load. Multithreading questions expose that grasp quickly, because a wrong mental model of visibility or atomicity produces confident but broken answers.
This page covers the questions that recur in real Java loops, from junior definitions to the memory-model follow-ups that separate levels. Answer from the shared state outward, and name the exact guarantee — atomicity, visibility, ordering — you are relying on.
How to answer multithreading questions
Before proposing a fix, identify the shared mutable state and which threads touch it. "This counter is read and written by request threads" tells the interviewer you see the actual problem. Then reach for the cheapest correct tool and justify it — immutability, then java.util.concurrent, then locks. Reciting synchronized reflexively, without naming what it protects, is the answer that stalls under the first follow-up.
Q1. What are the ways to create a thread, and which do you prefer?
You can extend Thread, implement Runnable, or — the modern default — submit a Runnable/Callable to an ExecutorService. Prefer Runnable/Callable with an executor: it separates the task from the thread, lets you reuse threads, and gives you futures and controlled shutdown.
Extending Thread couples your logic to a thread and burns your single inheritance slot. Runnable is just a task; the executor decides how and when it runs. Callable adds a return value and checked exceptions via Future.
ExecutorService pool = Executors.newFixedThreadPool(4);
Future<Integer> f = pool.submit(() -> compute()); // Callable
Integer result = f.get(); // blocks until done
pool.shutdown();
Interview note: Follow-up: "difference between
start()andrun()?"start()schedules the thread and the JVM callsrun()on the new stack; callingrun()directly just executes on the current thread — no concurrency at all.
Q2. What does the synchronized keyword actually guarantee?
synchronized provides mutual exclusion (only one thread holds the monitor at a time) and a memory guarantee (changes made under the lock are visible to the next thread that acquires it). It gives you both atomicity for the guarded block and visibility across threads.
Synchronizing on an instance method locks this; a static synchronized method locks the Class object; a synchronized block locks whatever object you name. The visibility half is the part candidates forget: releasing a monitor flushes writes, acquiring it invalidates stale cached reads — this is the happens-before edge that makes the guarded state correct.
Interview note: Trap: "two synchronized methods on different objects — do they block each other?" No. The lock is per-object; different instances have different monitors, so both run concurrently.
Q3. What does volatile do, and what does it NOT do?
volatile guarantees visibility and ordering for a single variable: a write is immediately visible to other threads, and reads/writes are not reordered around it. It does NOT provide atomicity for compound actions like count++.
count++ is read-modify-write — three steps — so two threads can both read the same value and one increment is lost, even on a volatile field. Use volatile for a flag written by one thread and read by others (volatile boolean running), and AtomicInteger or a lock when you need atomic updates.
private volatile boolean running = true; // fine: single write, many reads
public void stop() { running = false; } // visible to the worker loop
Interview note: Follow-up: "why does double-checked locking need volatile?" Without it, another thread can see a non-null but partially constructed object because the constructor's writes were reordered after the reference assignment.
Q4. Explain wait(), notify() and notifyAll().
wait() releases the object's monitor and parks the thread until another thread calls notify()/notifyAll() on the same object; then it re-acquires the monitor before returning. They must be called inside a synchronized block on that object, and you always wait in a loop that rechecks the condition.
The loop matters because of spurious wakeups and because the condition may have changed between the notify and the reacquire. notify() wakes one waiter (risking lost signals if waiters wait on different conditions); notifyAll() wakes all — safer by default.
synchronized (queue) {
while (queue.isEmpty()) { // loop, never a bare if
queue.wait();
}
return queue.poll();
}
Interview note: Trap: "why
whileand notif?" A spurious wakeup or a competing consumer can leave the condition false whenwait()returns;ifwould proceed on a false condition.
Q5. synchronized vs ReentrantLock — when do you choose the Lock?
synchronized is simpler and auto-releases on block exit, including exceptions. ReentrantLock adds capabilities: tryLock() with a timeout, interruptible locking, fairness, and multiple Condition objects. Choose the Lock when you need those; otherwise synchronized reads more cleanly.
The trade-off is safety versus power. With a Lock you must unlock in a finally, or a thrown exception leaks the lock forever. tryLock is the feature that most often justifies it — attempting to acquire without blocking indefinitely is how you avoid certain deadlocks.
if (lock.tryLock(2, TimeUnit.SECONDS)) {
try { /* critical section */ }
finally { lock.unlock(); } // MUST be in finally
}
Interview note: Follow-up: "what does reentrant mean?" The same thread can acquire the lock it already holds without deadlocking; a hold count tracks the nesting and only the final unlock releases it.
Q6. How does a thread pool work, and how do you size one?
An ExecutorService keeps a set of worker threads pulling tasks off a queue, so you pay thread-creation cost once instead of per task. Size it by workload: CPU-bound work wants roughly the core count; I/O-bound work wants more threads because each spends most of its time blocked and idle.
A rough starting formula for I/O-bound work is threads = cores × (1 + waitTime/computeTime). The other half of the answer is the queue and rejection policy: an unbounded queue hides overload until you run out of memory, so bounded queues plus a sensible rejection policy make backpressure visible.
Interview note: Trap: "what does
Executors.newFixedThreadPooluse for its queue?" An unboundedLinkedBlockingQueue— which is exactly why production code often builds aThreadPoolExecutordirectly with a bounded queue.
Q7. What is a deadlock, and how do you prevent it?
A deadlock is a cycle of threads each holding a lock the next one needs, so none can proceed. The classic prevention is global lock ordering: always acquire locks in the same order everywhere, which makes a cycle impossible.
The four Coffman conditions (mutual exclusion, hold-and-wait, no preemption, circular wait) must all hold; breaking any one prevents deadlock. Practically: order your locks, use tryLock with a timeout to back off, and hold locks for the shortest time possible. Livelock (threads busy but not progressing) and starvation are the related failures interviewers may probe.
Interview note: Follow-up: "how do you detect a deadlock in production?" A thread dump (
jstack) shows threadsBLOCKEDon monitors with a "found one Java-level deadlock" section naming the cycle.
Q8. Why prefer ConcurrentHashMap over a synchronized HashMap?
Collections.synchronizedMap wraps every method in one lock, so all access serializes and compound actions (containsKey then put) are still racy. ConcurrentHashMap allows concurrent reads and fine-grained locked writes, and offers atomic compound operations like putIfAbsent, compute and merge.
The deeper point is that per-method thread safety does not compose into correct logic — the check-then-act gap is a race regardless of how each individual method is synchronized. ConcurrentHashMap's atomic methods exist precisely to close that gap in one call.
counts.merge(key, 1, Integer::sum); // atomic increment, no external lock
Interview note: Trap: "does ConcurrentHashMap allow null keys or values?" No — a null return from
getwould be ambiguous under concurrent access, so both are forbidden.
Q9. What is the Java Memory Model in one paragraph?
The Java Memory Model defines the happens-before relationship that determines when one thread's writes are guaranteed visible to another. Without a happens-before edge — established by synchronized, volatile, Thread.start/join, or the concurrent utilities — threads may observe stale or reordered values, because the JVM and CPU are free to cache and reorder for speed.
Most concurrency bugs are visibility or ordering bugs, not just missing mutual exclusion. Framing your answers around "does a happens-before edge exist here?" is what makes them sound senior rather than keyword-driven.
Interview note: Follow-up: "give a happens-before edge people forget."
Thread.start()happens-before everything the new thread does, and everything a thread does happens-before another thread's successfuljoin()on it.
How to prepare
Write the failing versions yourself: a lost-update counter without AtomicInteger, a visibility bug where a non-volatile flag never stops a loop, and a two-lock deadlock you then fix with lock ordering. Each takes minutes and burns the concept in permanently. Then rehearse thread-pool sizing out loud on a stated workload, since "how many threads?" is a favorite that rewards reasoning over a memorized number.
Pair this with the broader Java concurrency question set for CompletableFuture and the concurrent utilities, and if you are interviewing at a senior band see the senior developer questions for how concurrency shows up in design rounds. A mock interview focused on threading is the fastest way to find where your memory-model reasoning runs out — and push it one layer deeper. Start from the Java learning path if you need to rebuild the fundamentals first.
Frequently Asked Questions
What multithreading topics are asked most often in Java interviews?
Is multithreading asked to freshers or only experienced developers?
What is the difference between concurrency and parallelism?
How should I answer a 'how do you make this thread-safe' question?
Are virtual threads a common interview topic now?
Want to Build Your Career in Java Full Stack with AI?
Join CodeBegun and train with working industry engineers — Check the Java Full Stack training details

