Top 50 Java Multithreading Interview Questions and Expert Answers

Illustration for Top 50 Java Multithreading Interview Questions and Expert Answers
By Last updated:

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:

  1. By extending the Thread class:
class MyThread extends Thread {
    public void run() {
        System.out.println("Thread is running");
    }
}
new MyThread().start();
  1. By implementing the Runnable interface:
class MyRunnable implements Runnable {
    public void run() {
        System.out.println("Runnable thread");
    }
}
new Thread(new MyRunnable()).start();
  1. 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:

  1. NEW – Thread object is created but not started.
  2. RUNNABLE – Thread is ready to run and waiting for CPU.
  3. RUNNING – Thread is currently executing.
  4. BLOCKED – Thread is waiting to acquire a lock.
  5. WAITING – Thread is waiting indefinitely for another thread to perform an action.
  6. TIMED_WAITING – Thread is waiting for a specified time.
  7. 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 the run() 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:

  1. synchronized method:
public synchronized void increment() { count++; }
  1. synchronized block:
synchronized (lock) { count++; }
  1. 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 result
  • isDone() – Checks if task is complete
  • cancel() – 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 or CountDownLatch

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 and StructuredTaskScope 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.