Multithreading Interview Questions in Java
A school kitchen preparing lunch for a thousand students doesn't have one cook doing everything in turn. Several cooks work at once, which is fast, until two of them reach for the same pot at the same moment, or each waits for the other to hand over a ladle and nothing moves. Multithreading brings the same speed and the same risks to programs, which is why interviewers use it to find candidates who understand what really happens when code runs in parallel. This page collects the questions asked most often, from basic to advanced.
Basic Questions
What is the difference between a process and a thread?
A process is a running program with its own memory. A thread is a path of execution inside a process; threads of the same process share its heap, which makes communication easy and also makes data races possible.
How do you create a thread?
Pass a Runnable to a Thread and call start(), extend Thread and override run(), or, best in modern code, submit tasks to an ExecutorService. Since Java 21, Thread.ofVirtual().start(task) creates a virtual thread.
What is the difference between start() and run()?
start() creates a new thread that then calls run(). Calling run() directly just runs the method on the current thread, like any other method call. Calling start() twice throws IllegalThreadStateException.
What are the states of a thread?
NEW (created), RUNNABLE (running or ready), BLOCKED (waiting for a monitor lock), WAITING and TIMED_WAITING (waiting for another thread or a timeout, such as join or sleep), and TERMINATED.
Intermediate Questions
What is a race condition?
When the result depends on the timing of threads that access shared data without coordination. count++ is a classic example: it is a read, an add and a write, so two threads can both read the same old value and one update is lost.
What does synchronized do?
It makes a thread acquire an object's monitor lock before running the block or method, so only one thread at a time runs code guarded by that lock. It also guarantees that changes made inside are visible to the next thread that takes the same lock.
What is the difference between synchronized and volatile?
volatile guarantees visibility: every read sees the latest write. It doesn't give atomicity or mutual exclusion. synchronized gives both. Use volatile for simple flags; use locks or atomic classes for compound actions such as count++.
What is the difference between wait() and sleep()?
wait() is called on an object inside synchronized, releases the lock, and waits until notify or notifyAll. Thread.sleep() pauses the current thread for a time and keeps any locks it holds.
What is a deadlock, and how do you avoid it?
Two or more threads each hold a lock and wait for a lock the other holds, so none can continue. Avoid it by always taking locks in the same order, holding locks for as short a time as possible, or using tryLock with a timeout.
Advanced Questions
Why use an ExecutorService instead of creating threads?
Creating platform threads is expensive. A thread pool reuses a fixed number of threads, queues extra tasks, returns results through Future, and can be shut down cleanly. For many blocking tasks, Executors.newVirtualThreadPerTaskExecutor() gives one cheap virtual thread per task.
What is the difference between Runnable and Callable?
Runnable.run() returns nothing and can't throw checked exceptions. Callable.call() returns a value and can throw. Submitting a Callable to an executor gives a Future with the result.
What is CompletableFuture?
A Future that can be completed and chained: supplyAsync(...).thenApply(...).thenCombine(...).exceptionally(...). It lets you compose asynchronous steps without blocking a thread on each one.
What are virtual threads, and when should you use them?
Lightweight threads (final in Java 21) managed by the JVM. When a virtual thread blocks on I/O, it releases its carrier platform thread, so you can have hundreds of thousands of them. Use them for tasks that mostly wait (web calls, database queries); they don't speed up CPU-bound work.
What are atomic classes?
Classes such as AtomicInteger and AtomicLong that update a value atomically without locks, using the CPU's compare-and-set instruction: counter.incrementAndGet() is safe from many threads. LongAdder scales even better under heavy contention.
A Race Condition, Fixed
Let's see the classic lost-update race and two fixes.
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class Counter {
static int plain = 0;
static int locked = 0;
static final Object LOCK = new Object();
static final AtomicInteger atomic = new AtomicInteger();
public static void main(String[] args) throws InterruptedException {
ExecutorService pool = Executors.newFixedThreadPool(4);
for (int t = 0; t < 4; t++) {
pool.submit(() -> {
for (int i = 0; i < 100_000; i++) {
plain++; // not safe
synchronized (LOCK) { locked++; } // safe: one at a time
atomic.incrementAndGet(); // safe: lock-free
}
});
}
pool.shutdown();
pool.awaitTermination(10, TimeUnit.SECONDS);
System.out.println("expected: 400000");
System.out.println("synchronized: " + locked);
System.out.println("atomic: " + atomic.get());
System.out.println("plain lost updates? " + (plain < 400_000));
}
}expected: 400000
synchronized: 400000
atomic: 400000
plain lost updates? trueExplanation: four threads each add 100,000 to three counters. The synchronized and AtomicInteger counters always reach 400,000. The plain int almost always ends up lower, because increments from different threads overwrite each other. The exact number changes from run to run, which is why the output above is marked as varying.
Interview Corner
How would you make a class thread-safe?
In order of preference: make it immutable; avoid sharing (confine data to one thread); use thread-safe building blocks (ConcurrentHashMap, atomics); and only then add locking around compound actions, keeping the locked sections small.
How do you stop a thread?
Ask it to stop: call interrupt(), and have the thread check Thread.currentThread().isInterrupted() or handle InterruptedException and exit cleanly. Thread.stop() is unsafe and no longer works.
Practice
- Easy: Write a program with two threads that print "Navika" and "Srihitha" five times each with
Thread.sleep(10)between prints. Usejoin()somainprints "done" last. - Harder: Create a deadlock on purpose with two locks taken in opposite orders, observe the hang (stop it with Ctrl+C), then fix it by taking the locks in the same order.
Key Takeaways
Was this tutorial helpful?