Multithreading is a fundamental concept for building high-performance and responsive Java applications. Interviewers often focus heavily on multithreading because it tests a developer’s understanding of concurrency, thread safety, and performance optimization.
This guide provides 50 carefully selected and in-depth multithreading interview questions with answers—covering beginner to advanced topics including the Java Memory Model, java.util.concurrent
, synchronization mechanisms, and Java 21 enhancements like virtual threads and structured concurrency.
1. What is multithreading in Java and why is it useful?
Multithreading in Java is a programming technique where multiple threads run concurrently within a program to perform tasks independently. A thread is the smallest unit of execution, and multithreading allows multiple tasks to be processed in parallel or concurrently, improving performance and responsiveness.
For example, a web server can handle multiple client requests simultaneously using a pool of threads. Without multithreading, each request would have to be processed sequentially, slowing down the system.
Java supports multithreading via the Thread
class, Runnable
interface, and the java.util.concurrent
package. It’s particularly useful for:
- I/O operations (file, network)
- UI responsiveness (e.g., Android, Swing)
- Real-time systems (e.g., trading platforms)
2. What is the difference between a process and a thread?
A process is an independent execution unit with its own memory space, while a thread is a smaller unit of execution within a process that shares memory and resources with other threads of the same process.
Feature | Process | Thread |
---|---|---|
Memory | Has separate memory | Shares memory with other threads |
Overhead | Higher (context switching) | Lower |
Communication | Inter-process communication | Shared variables |
Isolation | Completely isolated | Not isolated |
In Java, threads are lighter and more efficient than processes and are preferred for concurrent tasks within the same application.
3. How do you create a thread in Java?
You can create a thread in Java in three main ways:
- By extending the
Thread
class:
class MyThread extends Thread {
public void run() {
System.out.println("Thread is running");
}
}
new MyThread().start();
- By implementing the
Runnable
interface:
class MyRunnable implements Runnable {
public void run() {
System.out.println("Runnable thread");
}
}
new Thread(new MyRunnable()).start();
- Using lambda expressions (Java 8+):
new Thread(() -> System.out.println("Lambda thread")).start();
The preferred way is using Runnable
or Callable
for better decoupling and flexibility.
4. What is the lifecycle of a Java thread?
A Java thread goes through the following states:
- NEW – Thread object is created but not started.
- RUNNABLE – Thread is ready to run and waiting for CPU.
- RUNNING – Thread is currently executing.
- BLOCKED – Thread is waiting to acquire a lock.
- WAITING – Thread is waiting indefinitely for another thread to perform an action.
- TIMED_WAITING – Thread is waiting for a specified time.
- TERMINATED – Thread has completed or exited due to error.
You can check the state of a thread using thread.getState()
.
5. What is the difference between Runnable
and Callable
?
Feature | Runnable | Callable |
---|---|---|
Return Type | Does not return result (void ) |
Returns a result (V ) |
Exceptions | Cannot throw checked exceptions | Can throw checked exceptions |
Interface | public interface Runnable |
public interface Callable<V> |
Use Case | Used for simple tasks | Used for tasks that return result |
Callable
is commonly used with ExecutorService.submit()
to retrieve results asynchronously using Future
.
6. What is the difference between start()
and run()
in Java threads?
start()
creates a new thread and invokes therun()
method in a new call stack.run()
is a normal method call and does not create a new thread.
Example:
Thread t = new Thread(() -> System.out.println(Thread.currentThread().getName()));
t.run(); // runs in main thread
t.start(); // runs in a new thread
Always use start()
to execute a thread asynchronously.
7. What are daemon threads in Java?
Daemon threads are background threads that do not prevent the JVM from exiting. They are used for low-priority tasks like garbage collection or monitoring.
You can create one by calling setDaemon(true)
before starting the thread:
Thread t = new Thread(task);
t.setDaemon(true);
t.start();
If all user threads finish execution, JVM will terminate even if daemon threads are still running.
8. What is thread starvation and how do you prevent it?
Thread starvation occurs when low-priority threads are constantly denied CPU access because high-priority threads monopolize it.
Causes:
- Thread priorities misused
- Locks never released
- Busy-wait loops
Solutions:
- Use fair locks (
new ReentrantLock(true)
) - Avoid setting unnecessary priorities
- Apply proper scheduling with thread pools
9. What is a race condition?
A race condition happens when two or more threads access shared data and try to modify it concurrently, and the final outcome depends on the timing of the threads.
Example:
int counter = 0;
Thread t1 = new Thread(() -> counter++);
Thread t2 = new Thread(() -> counter++);
Both threads might read the same value, leading to incorrect final value.
Solution: Synchronize access using synchronized
, ReentrantLock
, or atomic variables.
10. What is synchronization in Java and how is it implemented?
Synchronization ensures mutual exclusion by allowing only one thread to access a block of code or object at a time.
Ways to synchronize:
synchronized
method:
public synchronized void increment() { count++; }
synchronized
block:
synchronized (lock) { count++; }
ReentrantLock
:
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
Use synchronization to avoid race conditions.
11. What is a deadlock?
A deadlock is a situation where two or more threads are blocked forever, each waiting on a resource held by the other.
Example:
synchronized (obj1) {
synchronized (obj2) {
// logic
}
}
If another thread does synchronized (obj2)
followed by obj1
, both threads can be deadlocked.
Avoid using:
- Nested locks
- Circular wait
Prevention:
- Lock ordering
- Try-lock with timeout
12. What is the Java Memory Model (JMM)?
The JMM defines how threads interact through memory and what behaviors are allowed in a multithreaded Java program.
Key Concepts:
- Visibility: Changes by one thread visible to others.
- Atomicity: Operations that appear indivisible.
- Happens-before relationship: Rules that determine visibility order.
volatile
and synchronized
are tools to enforce visibility guarantees.
13. What is the volatile
keyword and when should you use it?
The volatile
keyword ensures that changes to a variable are immediately visible to other threads. It prevents caching of variables in thread-local memory.
Use when:
- Variable is accessed by multiple threads.
- Updates are atomic or independent.
Example:
private volatile boolean running = true;
Note: volatile
doesn’t ensure atomicity for compound actions like count++
.
14. How does Thread.sleep()
work?
Thread.sleep(milliseconds)
pauses the current thread for a specified duration without releasing locks.
Example:
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
- Throws
InterruptedException
if interrupted. - Moves thread to
TIMED_WAITING
state.
15. What is thread pooling and why is it preferred?
Thread pooling is the reuse of a fixed number of threads to execute many tasks, avoiding the overhead of creating new threads.
Benefits:
- Improves performance
- Reduces resource usage
- Enables better control
Use Executors:
ExecutorService pool = Executors.newFixedThreadPool(10);
pool.submit(() -> System.out.println("Task"));
Use shutdown()
to properly terminate the pool.
16. What is ExecutorService
in Java?
ExecutorService
is a high-level API introduced in java.util.concurrent
that simplifies thread management by decoupling task submission from thread creation.
Benefits:
- Thread reuse via pooling
- Better error handling with
Future
- Lifecycle control (
shutdown()
)
Example:
ExecutorService service = Executors.newFixedThreadPool(5);
service.submit(() -> System.out.println("Running task"));
service.shutdown();
Common implementations: FixedThreadPool
, CachedThreadPool
, ScheduledThreadPool
.
17. What is a Future
in Java?
A Future<T>
represents the result of an asynchronous computation.
Key methods:
get()
– Waits and returns resultisDone()
– Checks if task is completecancel()
– Attempts to cancel execution
Example:
Future<Integer> result = executor.submit(() -> 42);
Integer value = result.get(); // blocks
Use Callable<T>
to return results from threads.
18. What is a CountDownLatch
?
A CountDownLatch
is a synchronization aid that allows one or more threads to wait until a set of operations being performed in other threads completes.
Example:
CountDownLatch latch = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
new Thread(() -> {
// task
latch.countDown();
}).start();
}
latch.await(); // waits until count = 0
Useful in scenarios like waiting for multiple services to start.
19. What is a CyclicBarrier
?
A CyclicBarrier
lets multiple threads wait for each other to reach a common barrier point. Once all have arrived, they proceed.
Example:
CyclicBarrier barrier = new CyclicBarrier(3);
Runnable task = () -> {
System.out.println("Waiting...");
barrier.await(); // wait for others
System.out.println("Proceeding...");
};
Unlike CountDownLatch
, it can be reused.
20. What is ReentrantLock
?
ReentrantLock
is a flexible and powerful alternative to synchronized
. It provides:
- Fairness
- Interruptible lock acquisition
- Try-lock with timeout
Example:
ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
// critical section
} finally {
lock.unlock();
}
Always call unlock()
in a finally
block.
21. What is a ReadWriteLock
?
ReadWriteLock
allows multiple readers but only one writer at a time.
Usage:
ReadWriteLock rwLock = new ReentrantReadWriteLock();
Lock readLock = rwLock.readLock();
Lock writeLock = rwLock.writeLock();
It boosts performance when reads are frequent and writes are rare.
22. What is false sharing?
False sharing occurs when multiple threads write to variables that reside on the same cache line, causing performance degradation due to cache invalidation.
Even though the variables are independent, the CPU invalidates the whole cache line.
Solution:
- Use padding (
@Contended
in Java 8+ with JVM flag) - Avoid frequently updated shared variables in close memory proximity.
23. What is lock contention?
Lock contention occurs when multiple threads try to acquire the same lock, leading to blocked threads and performance drops.
Symptoms:
- Increased CPU time waiting
- Throughput bottlenecks
Mitigation strategies:
- Reduce lock scope and duration
- Use lock-free data structures (
ConcurrentHashMap
,Atomic*
) - Apply finer-grained locks
24. What is busy-waiting and why should it be avoided?
Busy-waiting is a situation where a thread continuously checks a condition in a loop, consuming CPU cycles unnecessarily.
Example:
while (!condition) {}
Problems:
- Wastes CPU time
- Reduces performance
Better alternatives:
- Use
wait()
/notify()
- Use
BlockingQueue
orCountDownLatch
25. What are thread-safe classes in Java?
Thread-safe classes are designed to be safely accessed by multiple threads concurrently without causing inconsistent or unpredictable results.
Examples:
Vector
,Hashtable
(legacy)ConcurrentHashMap
CopyOnWriteArrayList
AtomicInteger
,AtomicReference
These classes manage synchronization internally to ensure thread safety.
26. What is ThreadLocal
in Java?
ThreadLocal
provides thread-local variables, ensuring that each thread accessing it has its own independent copy.
Example:
ThreadLocal<Integer> threadId = ThreadLocal.withInitial(() -> 1);
Useful for user sessions, transactions, or date formatting without synchronization.
27. How does join()
work in Java threads?
The join()
method blocks the current thread until the thread on which it was called finishes.
Example:
Thread t = new Thread(task);
t.start();
t.join(); // main waits for t to finish
Used for sequencing and coordination between threads.
28. What are the risks of using stop()
, suspend()
, and resume()
?
These methods are deprecated due to serious problems:
stop()
can leave shared resources in inconsistent state.suspend()
can cause deadlocks if it suspends a thread holding a lock.resume()
may be invoked when the thread isn’t suspended.
Use interruption (interrupt()
and isInterrupted()
) for safe cancellation.
29. What is an atomic operation in Java?
An atomic operation completes in a single step relative to other threads. No thread can observe the operation in an intermediate state.
Examples of atomic types: AtomicInteger
, AtomicBoolean
.
Example:
AtomicInteger count = new AtomicInteger();
count.incrementAndGet(); // atomic increment
Atomic operations are critical for building lock-free thread-safe algorithms.
30. What is the Fork/Join framework in Java?
Introduced in Java 7, the Fork/Join framework helps in parallel processing by recursively breaking tasks into smaller subtasks.
Key classes:
ForkJoinPool
RecursiveTask<V>
(returns result)RecursiveAction
(no result)
Example:
ForkJoinPool pool = new ForkJoinPool();
pool.invoke(new MyTask());
Best suited for divide-and-conquer algorithms.
31. How do you handle exceptions in threads?
For threads created via Thread
, use setUncaughtExceptionHandler()
:
Thread t = new Thread(task);
t.setUncaughtExceptionHandler((thread, ex) -> System.out.println("Error: " + ex));
For ExecutorService
, use Future.get()
which throws exceptions wrapped in ExecutionException
.
32. What is BlockingQueue
?
A BlockingQueue
is a thread-safe queue that blocks when inserting into a full queue or retrieving from an empty queue.
Implementations:
ArrayBlockingQueue
LinkedBlockingQueue
PriorityBlockingQueue
Used in producer-consumer problems to decouple producers and consumers.
33. What is structured concurrency (Java 21)?
Structured concurrency allows threads to be scoped and managed like structured code blocks, making concurrency easier to reason about.
Benefits:
- Clear lifecycles
- Easier error handling and cancellation
Introduced in Java 21 under java.util.concurrent.StructuredTaskScope
.
34. What is the difference between Thread.yield()
and Thread.sleep()
?
Thread.yield()
suggests to the scheduler to pause the current thread and let others execute (but it's not guaranteed).Thread.sleep()
pauses for a fixed time and guarantees blocking.
Use sleep()
for precise delays, and yield()
for improving fairness (rarely used in practice).
35. What is the purpose of CompletableFuture
?
CompletableFuture
allows writing asynchronous, non-blocking code using callbacks and fluent chaining.
Features:
- Combine multiple futures
- Exception handling (
exceptionally()
) - Async execution (
thenApplyAsync()
)
Example:
CompletableFuture.supplyAsync(() -> "Hello")
.thenApply(String::toUpperCase)
.thenAccept(System.out::println);
Useful in concurrent web services and data pipelines.
36. What is ThreadGroup
and should you use it?
ThreadGroup
is a legacy class used to group multiple threads for management purposes like setting priorities or handling exceptions.
Example:
ThreadGroup group = new ThreadGroup("MyGroup");
new Thread(group, task).start();
Drawbacks:
- No longer recommended
ExecutorService
andStructuredTaskScope
offer better control
37. What is the difference between synchronized
and Lock
?
Feature | synchronized | Lock (ReentrantLock ) |
---|---|---|
Flexibility | Less | More |
Try-lock | No | Yes |
Fairness policy | No | Yes (new ReentrantLock(true) ) |
Interruptibility | No | Yes |
Condition support | No | Yes (Condition.await() ) |
Use Lock
when you need advanced control or fairness.
38. What are virtual threads (Java 21)?
Virtual threads (Project Loom) are lightweight threads managed by the JVM rather than the OS, enabling millions of concurrent threads.
Usage:
Thread.startVirtualThread(() -> System.out.println("Virtual!"));
They improve scalability and are ideal for I/O-heavy apps (e.g., web servers).
39. What is Thread.interrupt()
used for?
interrupt()
is used to signal a thread to stop what it’s doing and do something else (e.g., exit gracefully).
Threads must periodically check Thread.interrupted()
or handle InterruptedException
.
Example:
while (!Thread.currentThread().isInterrupted()) {
// work
}
Always design threads to be interruptible.
40. What are wait()
, notify()
, and notifyAll()
?
These are methods from Object
used for inter-thread communication.
wait()
– Causes thread to wait until notified.notify()
– Wakes up one waiting thread.notifyAll()
– Wakes up all waiting threads.
Example:
synchronized(lock) {
while (!condition) lock.wait();
// proceed
lock.notify();
}
Use wait()
inside loops to recheck conditions.
41. What is ThreadFactory
and why use it?
A ThreadFactory
is used to customize thread creation logic (e.g., naming threads, setting daemon flag).
Example:
ThreadFactory factory = r -> {
Thread t = new Thread(r);
t.setName("Worker-" + counter.getAndIncrement());
return t;
};
Pass it to Executors.newFixedThreadPool()
for better thread traceability.
42. What is the difference between invokeAny()
and invokeAll()
?
invokeAll()
runs all tasks and returns a list of Futures once all are complete.invokeAny()
returns result of the first successfully completed task and cancels others.
Example:
executor.invokeAny(tasks); // returns one result
executor.invokeAll(tasks); // returns List<Future>
43. How does Thread.setPriority()
work?
You can set thread priority using setPriority(int)
where 1 (MIN) to 10 (MAX).
Example:
Thread t = new Thread(task);
t.setPriority(Thread.MAX_PRIORITY);
However, priority handling is OS-dependent and may not be respected.
44. What is thread confinement?
Thread confinement ensures that data is only accessed by a single thread, avoiding the need for synchronization.
Techniques:
- Local variables
ThreadLocal
- Task-based isolation (e.g., submit task to a single-threaded executor)
45. What is a spurious wakeup?
A spurious wakeup is when a thread waiting on wait()
or Condition.await()
wakes up without being notified.
Best Practice: Always call wait()
in a loop checking the condition:
synchronized (lock) {
while (!condition) lock.wait();
}
This protects against premature wake-ups.
46. What is a livelock?
A livelock occurs when threads are not blocked but keep retrying an action and fail to make progress.
Example: Two threads repeatedly yielding to each other without finishing work.
Solution:
- Use backoff strategies
- Limit retries
- Design robust coordination logic
47. What is the difference between concurrency and parallelism?
- Concurrency is the ability to start, run, and complete multiple tasks in overlapping time periods.
- Parallelism is executing multiple tasks at the same time using multiple processors.
Java supports both via multithreading and parallel streams. Use concurrency for responsiveness, and parallelism for performance.
48. How do you debug multithreading issues in Java?
Common debugging techniques:
- Use thread dumps (
jstack
,VisualVM
,jconsole
) - Log thread names and timestamps
- Use debuggers with breakpoints and watches
- Avoid relying on print statements alone
Also, test with tools like FindBugs, ThreadSanitizer, or stress/load tests.
49. What are lock-free data structures in Java?
Lock-free data structures avoid mutual exclusion and instead use atomic operations for thread safety.
Examples:
ConcurrentLinkedQueue
Atomic*
classes (CAS operations)LongAdder
,StampedLock
(for scalable counters)
These are useful in high-throughput, low-latency environments.
50. What are some best practices for writing multithreaded code?
- Prefer higher-level concurrency APIs (
ExecutorService
,ForkJoinPool
) - Minimize shared mutable state
- Use thread-safe collections
- Favor immutability
- Always handle interruptions
- Use timeouts with locks
- Avoid premature optimization
- Profile and test under realistic load
Clean concurrency code is simple, composable, and interrupt-safe.