Interview Help Desk Multithreading

Multithreading

Practice commonly asked Multithreading interview questions with clear answers and explanations.

15 Interview Questions

Multithreading Interview Questions

15 Questions
Q 61

What is CyclicBarrier and how does it differ from CountDownLatch?

Medium
Answer
CyclicBarrier is a reusable barrier where a set of threads must all wait for each other to reach a common barrier point (await()) before proceeding. Unlike CountDownLatch (which counts events and cannot be reset), CyclicBarrier can be reset and reused across multiple phases.
Explanation
CyclicBarrier can execute an optional barrier action runnable as soon as the last thread arrives at the barrier.
Code Example Java
CyclicBarrier barrier = new CyclicBarrier(3, () -> System.out.println("Phase complete!"));
Runnable task = () -> {
    System.out.println("Arrived at barrier");
    try { barrier.await(); } catch (Exception e) {}
};
for (int i = 0; i < 3; i++) new Thread(task).start();
Reference: Java Multithreading & Concurrency
Q 62

What is CountDownLatch and how does it coordinate multiple threads?

Medium
Answer
CountDownLatch is a synchronization aid initialized with a given count. Threads call await() to block until the count reaches zero. Other worker threads call countDown() upon completing subtasks. Once the latch count reaches zero, all waiting threads are released simultaneously.
Explanation
CountDownLatch is a one-time use barrier; once the count reaches zero, it cannot be reset (use CyclicBarrier for reusable scenarios).
Code Example Java
CountDownLatch latch = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
    new Thread(() -> {
        System.out.println("Worker completed task");
        latch.countDown();
    }).start();
}
latch.await(); // Main thread blocks until all 3 workers call countDown()
System.out.println("All workers finished!");
Reference: Java Multithreading & Concurrency
Q 63

How does ThreadPoolExecutor work internally and what are its core tuning parameters?

Hard
Answer
ThreadPoolExecutor manages corePoolSize, maximumPoolSize, keepAliveTime, and a BlockingQueue workQueue. New tasks run on core threads; if core threads are busy, tasks buffer in the workQueue; if the workQueue fills up, new threads are created up to maximumPoolSize; further tasks trigger the RejectedExecutionHandler.
Explanation
Common RejectedExecutionHandler policies: AbortPolicy (throws exception), CallerRunsPolicy (executes task in submitter thread), DiscardPolicy (silently drops task), and DiscardOldestPolicy.
Code Example Java
ThreadPoolExecutor pool = new ThreadPoolExecutor(
    2, // corePoolSize
    4, // maximumPoolSize
    60L, TimeUnit.SECONDS, // keepAliveTime
    new ArrayBlockingQueue<>(100), // workQueue
    new ThreadPoolExecutor.CallerRunsPolicy() // rejectionHandler
);
Reference: Java Multithreading & Concurrency
Q 64

What is the difference between Callable and Runnable, and how does Future work?

Easy
Answer
Runnable's run() method returns void and cannot throw checked exceptions. Callable's call() method returns a generic result value (V) and can throw checked exceptions. A Future represents the pending asynchronous result of a Callable task (get(), isDone(), cancel()).
Explanation
Calling Future.get() blocks the calling thread until the worker computation finishes or reaches timeout.
Code Example Java
ExecutorService executor = Executors.newSingleThreadExecutor();
Callable<Integer> task = () -> { Thread.sleep(500); return 42; };
Future<Integer> future = executor.submit(task);
Integer result = future.get(); // Blocks until result is computed
executor.shutdown();
Reference: Java Multithreading & Concurrency
Q 65

What is the ExecutorService framework and how does it manage thread lifecycles?

Medium
Answer
ExecutorService decouples task submission from task execution using managed worker thread pools. It avoids the heavy overhead of continuously spawning new OS threads and provides lifecycle methods (submit, shutdown, shutdownNow, awaitTermination).
Explanation
Thread pools reuse active worker threads to process queued tasks, preventing thread exhaustion and OutOfMemoryError under heavy request spikes.
Code Example Java
ExecutorService executor = Executors.newFixedThreadPool(4);
executor.submit(() -> System.out.println("Task processed by pool worker"));
executor.shutdown(); // Graceful shutdown
Reference: Java Multithreading & Concurrency
Q 66

What is StampedLock (Java 8+) and what is Optimistic Reading?

Hard
Answer
StampedLock provides read/write locks backed by numeric stamps and introduces Optimistic Reading. An optimistic read obtains a stamp without acquiring an actual lock, performs read operations, and validates if a write lock occurred in between via validate(stamp).
Explanation
Optimistic reads do not block writers and writers do not block optimistic reads, offering superior performance when write contention is low. StampedLock is NOT reentrant.
Code Example Java
StampedLock lock = new StampedLock();
long stamp = lock.tryOptimisticRead();
double currX = x, currY = y;
if (!lock.validate(stamp)) { // Check if a write occurred
    stamp = lock.readLock(); // Fallback to pessimistic read lock
    try { currX = x; currY = y; } finally { lock.unlockRead(stamp); }
}
Reference: Java Multithreading & Concurrency
Q 67

What is ReadWriteLock (ReentrantReadWriteLock) and when is it preferred?

Hard
Answer
ReadWriteLock maintains a pair of locks: a ReadLock (shared by multiple concurrent reader threads when no write is occurring) and a WriteLock (exclusive to a single writer thread). It is preferred for read-heavy cache and dictionary data structures.
Explanation
It drastically increases throughput in scenarios where reads vastly outnumber writes because multiple reader threads execute simultaneously without blocking each other.
Code Example Java
ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
// Readers:
rwLock.readLock().lock();
try { return cache.get(key); } finally { rwLock.readLock().unlock(); }

// Writers:
rwLock.writeLock().lock();
try { cache.put(key, val); } finally { rwLock.writeLock().unlock(); }
Reference: Java Multithreading & Concurrency
Q 68

What is ReentrantLock and how does it differ from synchronized blocks?

Medium
Answer
ReentrantLock is an explicit lock from java.util.concurrent.locks offering advanced capabilities over synchronized: tryLock() (non-blocking acquisition with timeout), lockInterruptibly() (interruptible lock attempts), fairness policies, and multiple Condition variables.
Explanation
Always release ReentrantLock inside a finally block to prevent permanent lock abandonment if an exception occurs.
Code Example Java
ReentrantLock lock = new ReentrantLock(true); // Fair lock
lock.lock();
try {
    // Critical section code
} finally {
    lock.unlock(); // Guaranteed release
}
Reference: Java Multithreading & Concurrency
Q 69

What is Deadlock and what are the four conditions required for a Deadlock to occur?

Hard
Answer
A Deadlock is a situation where two or more threads are blocked forever, each waiting for a lock held by the other. The four Coffman conditions are: Mutual Exclusion, Hold and Wait, No Preemption, and Circular Wait.
Explanation
Deadlocks can be prevented by acquiring locks in a consistent, global order or using tryLock() with timeouts in ReentrantLock.
Code Example Java
// Deadlock occurs when Thread 1 acquires lockA then lockB,
// while Thread 2 acquires lockB then lockA concurrently:
// Fix: Always acquire locks in consistent order (lockA -> lockB) in all threads.
Reference: Java Multithreading & Concurrency
Q 70

What is the difference between Thread.sleep() and Object.wait()?

Easy
Answer
Thread.sleep() pauses the current thread for a specified duration without releasing any acquired locks. Object.wait() releases the monitor lock on the target object and waits until notify()/notifyAll() is called or timeout expires.
Explanation
sleep() is a static method of Thread class and can be called anywhere; wait() is an instance method of Object class and must be called inside a synchronized block.
Code Example Java
// Sleep: Retains locks
Thread.sleep(1000);

// Wait: Releases lock on monitor
synchronized (monitor) {
    monitor.wait(1000);
}
Reference: Java Multithreading & Concurrency
Q 71

What is the difference between wait(), notify(), and notifyAll(), and why must they be called inside a synchronized context?

Medium
Answer
wait() releases the monitor lock and suspends the thread until notified; notify() wakes up one arbitrary waiting thread; notifyAll() wakes up all waiting threads. They must be called inside a synchronized block on the monitor object, otherwise IllegalMonitorStateException is thrown.
Explanation
Always invoke wait() inside a while loop (condition check) to guard against spurious wakeups.
Code Example Java
synchronized (lock) {
    while (!condition) {
        lock.wait(); // Releases lock and waits safely
    }
    // Perform state change
    lock.notifyAll(); // Wakes up all waiting threads
}
Reference: Java Multithreading & Concurrency
Q 72

What is the difference between synchronized block and synchronized method?

Medium
Answer
A synchronized method locks the entire method scope using the intrinsic lock of 'this' (instance method) or the Class object (static method). A synchronized block allows fine-grained locking on a specific monitor object only around critical code sections, minimizing thread contention.
Explanation
Synchronized blocks reduce lock holding time, resulting in significantly higher concurrency and throughput compared to whole-method locking.
Code Example Java
public class Counter {
    private int count = 0;
    private final Object lock = new Object(); // Dedicated monitor lock
    public void increment() {
        // Only synchronized inside the critical section
        synchronized (lock) {
            count++;
        }
    }
}
Reference: Java Multithreading & Concurrency
Q 73

What is the Java Memory Model (JMM) and what is the role of the 'volatile' keyword?

Hard
Answer
The JMM defines how threads interact through memory and hardware caches. The 'volatile' keyword guarantees variable visibility (reads/writes go directly to main memory, bypassing CPU L1/L2 caches) and establishes a happens-before relationship, preventing instruction reordering. It does NOT guarantee atomicity for compound operations.
Explanation
While volatile guarantees visibility for single reads/writes (like boolean flags), compound operations like count++ require AtomicInteger or synchronized locks for thread safety.
Code Example Java
public class Worker implements Runnable {
    private volatile boolean running = true; // Visibility guaranteed across CPU cores
    public void stop() { running = false; }
    public void run() {
        while (running) { /* Process tasks */ }
    }
}
Reference: Java Multithreading & Concurrency
Q 74

What is the difference between start() and run() methods in Thread?

Easy
Answer
start() allocates a new native OS thread, initializes runtime call stack resources, and invokes the run() method asynchronously. Calling run() directly executes the method synchronously in the caller's existing thread without spawning a new thread.
Explanation
Calling start() more than once on the same Thread instance throws an IllegalThreadStateException.
Code Example Java
Thread t = new Thread(() -> System.out.println(Thread.currentThread().getName()));
t.start(); // Spawns new thread (e.g. Thread-0)
// t.run(); // Runs in current caller thread (e.g. main)
Reference: Java Multithreading & Concurrency
Q 75

What is the difference between extending Thread class and implementing Runnable interface in Java?

Easy
Answer
Implementing Runnable is preferred because Java only supports single class inheritance, allowing the class to extend another parent class. Implementing Runnable also cleanly separates the task execution logic from the Thread infrastructure and integrates with ExecutorService thread pools.
Explanation
Extending Thread couples task code tightly to thread management. Runnable separates concern (Single Responsibility Principle) and allows sharing resource instances across worker threads.
Code Example Java
// Recommended: Implementing Runnable
Runnable task = () -> System.out.println("Running in: " + Thread.currentThread().getName());
Thread thread = new Thread(task);
thread.start();
Reference: Java Multithreading & Concurrency

About This Topic

Prepare for Multithreading interviews with important concepts and commonly asked questions.