Java Multithreading Interview Questions, Simplified: Mastering Java Threading Concepts
25 Java multithreading interview questions with short, precise answers: thread lifecycle, synchronized and volatile, locks, thread pools, ConcurrentHashMap, CompletableFuture and virtual threads.
New10 min read

Multithreading is the Java interview topic that separates candidates. Almost everyone can say "a thread is a lightweight process". Far fewer can explain why count++ is not thread-safe, what volatile actually guarantees, or when a thread pool rejects a task. This guide collects 25 Java multithreading interview questions on the core threading concepts, with short, precise answers and small code examples.
The questions are grouped the way interviews usually move: basics and the thread lifecycle, synchronization and the memory model, locks, executors, concurrent collections, CompletableFuture, and virtual threads. When you can answer the concepts, test yourself on code with the companion post, crucial Java threading queries for interviews.
Basics and the thread lifecycle
1. What is the difference between a process and a thread?
A process is a running program with its own memory space. A thread is a path of execution inside a process. Threads of the same process share the heap (objects, static fields), but each thread has its own stack, program counter and local variables. Sharing the heap is what makes threads cheap to communicate through, and also what makes them dangerous.
2. How do you create a thread in Java?
Pass a Runnable to a Thread (or extend Thread, which is rarely a good idea), submit a Runnable or Callable to an ExecutorService, or, since Java 21, start a virtual thread. In real code, prefer an executor over creating threads by hand.
Thread t = new Thread(() -> System.out.println("hello"), "worker-1");
t.start();
Thread v = Thread.ofVirtual().start(() -> System.out.println("virtual"));3. What is the difference between start() and run()?
start() creates a new thread, which then calls run(). Calling run() directly is just a normal method call on the current thread: nothing runs in parallel. Calling start() twice on the same Thread throws IllegalThreadStateException.
4. What are the states in the thread lifecycle?
The Thread.State enum has six states:
- NEW – created,
start()not called yet. - RUNNABLE – running, or ready to run and waiting for a CPU.
- BLOCKED – waiting to enter a
synchronizedblock or method whose monitor another thread holds. - WAITING – waiting with no timeout:
Object.wait(),join(),LockSupport.park(). - TIMED_WAITING – waiting with a timeout:
Thread.sleep(ms),wait(ms),join(ms). - TERMINATED –
run()has finished, normally or with an exception.
A follow-up interviewers like: a thread waiting for a ReentrantLock shows as WAITING (or TIMED_WAITING), not BLOCKED, because java.util.concurrent locks park the thread instead of using a monitor.

5. What is the difference between sleep() and wait()?
Thread.sleep()pauses the current thread for a time and keeps any locks it holds.Object.wait()must be called while holding that object's monitor. It releases the monitor and waits until another thread callsnotify()ornotifyAll()on the same object (or the timeout passes).
6. What does join() do?
t.join() makes the calling thread wait until t terminates. It also gives a memory guarantee: everything t wrote is visible to the thread after join() returns.
7. What is a daemon thread?
A background thread that does not keep the JVM alive: the JVM exits when only daemon threads are left. Call setDaemon(true) before start(), otherwise it throws IllegalThreadStateException. Do not give daemon threads work that must finish, such as writing a file, because they can be stopped mid-way at shutdown.
8. How do you stop a thread?
Cooperatively. Call interrupt(), and have the task check Thread.currentThread().isInterrupted() or handle InterruptedException. Blocking methods such as sleep() and wait() throw that exception and clear the interrupt flag, so if you catch it and cannot exit straight away, restore the flag. Thread.stop() is unsafe and no longer works in recent JDKs.
try {
Thread.sleep(1_000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // keep the signal for the caller
return;
}Synchronization and the Java memory model
9. What is a race condition?
A bug where the result depends on how threads happen to interleave. The classic example is count++: it is three steps (read, add, write), so two threads can both read 5 and both write 6, and one increment is lost.
10. What does synchronized lock on?
Every object has a monitor. A synchronized instance method locks this, a synchronized static method locks the Class object, and a synchronized (obj) block locks obj. Only one thread can hold a given monitor at a time, and monitors are reentrant: a thread that holds one can enter it again. Prefer a small block on a private lock object over synchronizing whole methods.
11. What does volatile guarantee?
Visibility and ordering: a write to a volatile field is visible to every thread that reads it afterwards, and reads and writes are not reordered around it. It does not make compound actions atomic, so volatile int count; count++ is still a race. Use volatile for a flag or a published reference; use a lock or an atomic class for read-modify-write.
12. What is a happens-before relationship?
The memory model's rule for when one thread is guaranteed to see another thread's writes. If action A happens-before action B, B sees A's effects. The main rules:
- Unlocking a monitor happens-before every later lock of the same monitor.
- A write to a
volatilefield happens-before every later read of it. Thread.start()happens-before everything in the started thread.- Everything in a thread happens-before another thread returns from
join()on it.
Without one of these edges, the JVM may let a thread see stale values indefinitely.
13. How do wait() and notify() work, and why call wait() in a loop?
Both must be called while holding the object's monitor, otherwise they throw IllegalMonitorStateException. Always wait in a while loop that re-checks the condition: a thread can wake up spuriously, or another thread can change the state between the notify and the wake-up.
synchronized (lock) {
while (queue.isEmpty()) {
lock.wait();
}
return queue.remove();
}In new code, a BlockingQueue or a Condition is usually clearer than raw wait/notify.
Locks
14. ReentrantLock or synchronized?
Both give mutual exclusion and the same visibility guarantees. ReentrantLock adds tryLock() (with or without a timeout), lockInterruptibly(), an optional fairness policy, and several Condition objects per lock. The cost is that you must unlock it yourself, always in finally:
lock.lock();
try {
balance -= amount;
} finally {
lock.unlock();
}Use synchronized for simple cases; reach for ReentrantLock when you need one of its extra features.
15. What is a ReadWriteLock?
A pair of locks: many threads can hold the read lock at once, but the write lock is exclusive. It helps when reads far outnumber writes and each read takes a while. For very short reads, the bookkeeping can cost more than it saves, so measure. StampedLock adds an optimistic read mode for the same case.
16. What are deadlock, livelock and starvation?
- Deadlock – threads wait for each other's locks forever, for example thread 1 holds A and wants B while thread 2 holds B and wants A. Prevent it by always acquiring locks in the same global order, or by using
tryLockwith a timeout. - Livelock – threads keep reacting to each other and retrying, so none makes progress although none is blocked.
- Starvation – a thread never gets the lock or CPU time it needs because others always win.
Executors and thread pools
17. Why use an ExecutorService instead of new Thread()?
Creating a platform thread is expensive and unbounded thread creation can exhaust memory. An executor reuses a fixed set of threads, queues work, gives you a Future for each result, and can be shut down cleanly. It also separates what to run from how it is run.
18. Runnable or Callable?
Runnable.run() returns nothing and cannot throw checked exceptions. Callable.call() returns a value and can throw. Submitting either to an executor returns a Future; future.get() waits for the result and wraps any exception from the task in an ExecutionException.
19. How does a ThreadPoolExecutor decide what to do with a new task?
- If fewer than
corePoolSizethreads are running, start a new thread. - Otherwise, put the task in the work queue.
- If the queue is full and fewer than
maximumPoolSizethreads are running, start a new thread. - Otherwise, reject the task. The default
AbortPolicythrowsRejectedExecutionException.
The trap: Executors.newFixedThreadPool(n) uses an unbounded queue, so it never rejects and the queue can grow until memory runs out. For servers, build a ThreadPoolExecutor with a bounded queue and a deliberate rejection policy.
20. What is the difference between shutdown() and shutdownNow()?
shutdown() stops accepting new tasks and lets queued and running tasks finish. shutdownNow() also interrupts running tasks and returns the queued tasks that never started. Neither waits; call awaitTermination() to wait.
Concurrent collections and atomics
21. ConcurrentHashMap, Hashtable or Collections.synchronizedMap?
Hashtable and synchronizedMap lock the whole map for every call. ConcurrentHashMap lets reads proceed without locking and, since Java 8, locks only the bin being updated, so it scales far better. Its iterators are weakly consistent and never throw ConcurrentModificationException. It does not allow null keys or values. Use its atomic methods (merge, compute, computeIfAbsent) instead of a separate get and put.
22. How do atomic classes work?
AtomicInteger, AtomicLong and AtomicReference use compare-and-set (CAS), a CPU instruction that updates a value only if it still holds the expected value, and retry if it does not. No lock is taken. Under very heavy contention on one counter, LongAdder is faster because it spreads updates over several cells and adds them up when you read.
23. What are CountDownLatch, CyclicBarrier and Semaphore for?
- CountDownLatch – one or more threads wait until a count reaches zero. One-shot.
- CyclicBarrier – a fixed number of threads wait for each other at a point, then all continue. Reusable.
- Semaphore – limits how many threads use a resource at once, for example 10 concurrent calls to a slow service.
CompletableFuture
24. Why use CompletableFuture over Future?
A plain Future only lets you block on get(). CompletableFuture lets you chain work that runs when the result arrives, combine several futures, and handle errors without blocking.
CompletableFuture<Order> order = CompletableFuture
.supplyAsync(() -> findUser(id), pool)
.thenCompose(user -> loadCartAsync(user)) // returns another CompletableFuture
.thenApply(cart -> toOrder(cart)) // plain transformation
.exceptionally(ex -> Order.failed(ex));thenApply maps the value; thenCompose flattens a function that itself returns a CompletableFuture (like flatMap). The *Async methods run on the common ForkJoinPool unless you pass an executor. Pass your own for blocking I/O so you do not starve the common pool.
Virtual threads (Java 21)
25. What are virtual threads and when do they help?
Virtual threads are lightweight threads managed by the JVM rather than the operating system. When one blocks on I/O, the JVM unmounts it from its carrier (platform) thread, which then runs another virtual thread. You can run hundreds of thousands of them, which makes the simple thread-per-request style scale for I/O-bound work.
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (String url : urls) {
executor.submit(() -> fetch(url));
}
} // close() waits for all submitted tasksThings interviewers check: virtual threads do not make CPU-bound code faster; do not pool them (create one per task); and limit access to scarce resources with a Semaphore, not a small pool. On Java 21, blocking inside a synchronized block pins the virtual thread to its carrier. JDK 24 removed most of that pinning; the code-level version of this question is in crucial Java threading queries.
How to prepare
- Answer out loud, then write code. For each concept, write the smallest program that shows it: a lost update, a deadlock, a pool that rejects a task.
- Learn the "why". "Use
ConcurrentHashMap" is half an answer; "because it locks per bin and has atomicmerge" is the whole one. - Practise scenarios. Most interviews end with "what does this code do?". Work through the scenario-based threading questions next.
Go further
For 100 of the most-asked questions with full answers and coding challenges, see Java Multithreading and Concurrency: 100 interview questions. Concurrency is week 3 of the 30-day Java interview roadmap, and the core Java interview questions cover the basics it builds on.






