Interview Help Desk Multithreading

Multithreading

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

30 Interview Questions

Multithreading Interview Questions

30 Questions
Q 31

What is ScheduledExecutorService and how does scheduleAtFixedRate differ from scheduleWithFixedDelay?

Medium
Answer
scheduleAtFixedRate executes tasks at periodic intervals calculated from the initiation time of the previous task (fixed clock schedule). scheduleWithFixedDelay executes tasks with a fixed delay interval between the completion of one task and the start of the next.
Explanation
If a task takes longer than the period in scheduleAtFixedRate, the next execution will start immediately, but will not run concurrently on the same worker.
Code Example Java
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
// Runs every 5 seconds regardless of task duration:
scheduler.scheduleAtFixedRate(() -> doWork(), 0, 5, TimeUnit.SECONDS);
// Waits 5 seconds after previous task finishes:
scheduler.scheduleWithFixedDelay(() -> doWork(), 0, 5, TimeUnit.SECONDS);
Reference: Java Multithreading & Concurrency
Q 32

What is AtomicIntegerFieldUpdater / AtomicReferenceFieldUpdater and why are they used in high-performance libraries?

Hard
Answer
AtomicFieldUpdaters are reflection-based utilities that enable volatile fields in existing classes to be updated via CAS without wrapping every field in separate AtomicInteger or AtomicReference object instances, eliminating millions of wrapper object allocations.
Explanation
High-performance frameworks like Netty use AtomicFieldUpdaters to save heap memory and reduce GC pressure.
Code Example Java
class Node {
    volatile int state = 0;
    private static final AtomicIntegerFieldUpdater<Node> UPDATER =
        AtomicIntegerFieldUpdater.newUpdater(Node.class, "state");

    void update() { UPDATER.compareAndSet(this, 0, 1); }
}
Reference: Java Multithreading & Concurrency
Q 33

What is Asynchronous Task Orchestration with CompletableFuture.allOf() vs CompletableFuture.anyOf()?

Medium
Answer
CompletableFuture.allOf() returns a new future that completes only when ALL provided futures finish. CompletableFuture.anyOf() returns a future that completes as soon as ANY ONE of the provided futures finishes.
Explanation
allOf is used to aggregate results across parallel microservices; anyOf is used for fastest-mirror retrieval patterns.
Code Example Java
CompletableFuture<String> f1 = CompletableFuture.supplyAsync(() -> "ServiceA");
CompletableFuture<String> f2 = CompletableFuture.supplyAsync(() -> "ServiceB");

CompletableFuture.allOf(f1, f2).thenRun(() -> {
    System.out.println("Both services responded!");
});
Reference: Java Multithreading & Concurrency
Q 34

What is the difference between Thread.isInterrupted() and Thread.interrupted()?

Easy
Answer
Thread.isInterrupted() is an instance method that queries the interrupted status without modifying it. Thread.interrupted() is a static method that checks the current thread's status AND clears the interrupted flag (resets to false).
Explanation
Calling Thread.interrupted() twice consecutively will always return false on the second call if the first returned true.
Code Example Java
Thread t = Thread.currentThread();
t.interrupt();
System.out.println(t.isInterrupted()); // true
System.out.println(Thread.interrupted()); // true (clears flag)
System.out.println(t.isInterrupted()); // false
Reference: Java Multithreading & Concurrency
Q 35

What is pinning in Virtual Threads (Java 21+) and why should synchronized blocks be replaced by ReentrantLock?

Hard
Answer
Pinning occurs when a Virtual Thread executes inside a 'synchronized' block/method or native call. During pinning, the Virtual Thread cannot unmount from its underlying carrier OS thread when blocking on I/O, stalling the OS carrier thread. Replacing synchronized with ReentrantLock avoids pinning.
Explanation
ReentrantLock allows the Virtual Thread to unmount seamlessly during blocking calls, maintaining high throughput.
Code Example Java
// AVOID in Virtual Threads (causes carrier thread pinning):
// synchronized (this) { makeBlockingNetworkCall(); }

// RECOMMENDED in Virtual Threads:
private final ReentrantLock lock = new ReentrantLock();
public void process() {
    lock.lock();
    try { makeBlockingNetworkCall(); } finally { lock.unlock(); }
}
Reference: Java Multithreading & Concurrency
Q 36

What is the Happens-Before relationship in the Java Memory Model?

Hard
Answer
The Happens-Before relationship guarantees that memory writes made by one action are visible to another specific action across threads. Examples include: unlock happens-before subsequent lock on same monitor; volatile write happens-before subsequent volatile read; Thread.start() happens-before any action in that thread.
Explanation
If two actions lack a happens-before relationship, the JVM and CPU are free to reorder instructions for performance optimization.
Code Example Java
// Volatile happens-before rule:
volatile boolean ready = false;
int data = 0;
// Thread 1: data = 42; ready = true; (write)
// Thread 2: if (ready) { assert data == 42; } (read guaranteed to see 42)
Reference: Java Multithreading & Concurrency
Q 37

What is RecursiveTask vs RecursiveAction in the Fork/Join Framework?

Medium
Answer
Both extend ForkJoinTask for parallel divide-and-conquer processing. RecursiveTask<V> returns a result value from compute(). RecursiveAction returns void (no result returned).
Explanation
Tasks fork subtasks via fork() and aggregate returned results via join().
Code Example Java
class SumTask extends RecursiveTask<Long> {
    protected Long compute() {
        if (size <= THRESHOLD) return computeDirectly();
        SumTask subtask = new SumTask(left);
        subtask.fork(); // Asynchronous split
        return new SumTask(right).compute() + subtask.join(); // Merge
    }
}
Reference: Java Multithreading & Concurrency
Q 38

What is the difference between Callable and Supplier in Java concurrency?

Easy
Answer
Callable<V> has call() which can throw checked exceptions and is designed for ExecutorService.submit(). Supplier<T> has get() which cannot throw checked exceptions and is designed for functional pipelines like CompletableFuture.supplyAsync().
Explanation
Callable belongs to java.util.concurrent (Java 5), while Supplier belongs to java.util.function (Java 8).
Code Example Java
// Callable in ExecutorService:
Callable<String> c = () -> { if (err) throw new Exception(); return "ok"; };

// Supplier in CompletableFuture:
Supplier<String> s = () -> "ok";
CompletableFuture.supplyAsync(s);
Reference: Java Multithreading & Concurrency
Q 39

What is AtomicMarkableReference and how does it differ from AtomicStampedReference?

Hard
Answer
AtomicStampedReference pairs an object reference with an integer stamp (version count). AtomicMarkableReference pairs an object reference with a single boolean mark flag (e.g., logically marked for deletion).
Explanation
AtomicMarkableReference is commonly used in lock-free linked lists to logically mark a node as deleted before physically removing it.
Code Example Java
AtomicMarkableReference<Node> node = new AtomicMarkableReference<>(targetNode, false);
// Logically mark node as deleted:
node.compareAndSet(targetNode, targetNode, false, true);
Reference: Java Multithreading & Concurrency
Q 40

What is the difference between Thread.join() and CountDownLatch?

Medium
Answer
Thread.join() blocks until a specific Thread object completely terminates and dies. CountDownLatch coordinates multiple threads without requiring thread termination; worker threads can continue executing subsequent work after calling countDown().
Explanation
CountDownLatch works seamlessly with pooled threads in ExecutorService, whereas Thread.join() cannot be used on reused thread pool workers.
Code Example Java
Thread t = new Thread(() -> doWork());
t.start();
t.join(); // Waits for thread 't' to finish execution and terminate
Reference: Java Multithreading & Concurrency
Q 41

How does Condition interface in java.util.concurrent.locks replace Object wait/notify?

Medium
Answer
A Condition instance is bound to a ReentrantLock and provides await(), signal(), and signalAll() methods. Unlike intrinsic monitor locks (which allow only one wait-set per object), a single Lock can have multiple Condition variables (e.g., notFull and notEmpty).
Explanation
Multiple Condition variables allow notifying only producers or only consumers specifically, eliminating unnecessary thread wakeups.
Code Example Java
Lock lock = new ReentrantLock();
Condition notFull = lock.newCondition();
Condition notEmpty = lock.newCondition();

// In consumer:
lock.lock();
try {
    while (count == 0) notEmpty.await(); // Waits only on notEmpty
    // ... extract item ...
    notFull.signal(); // Signals only waiting producers!
} finally { lock.unlock(); }
Reference: Java Multithreading & Concurrency
Q 42

What is a Thread Group in Java and why is it considered obsolete?

Easy
Answer
ThreadGroup was an early Java 1.0 mechanism to manage collections of threads as a single unit. It is obsolete because its methods are unsafe (e.g. stop, suspend, resume), its thread synchronization is flawed, and it has been completely superseded by the ExecutorService framework.
Explanation
Thread groups are no longer used in modern Java; use ExecutorService thread pools or Virtual Thread StructuredTaskScopes instead.
Code Example Java
// Obsolete legacy approach:
ThreadGroup group = new ThreadGroup("Workers");
Thread t = new Thread(group, () -> {}, "Worker-1");

// Modern approach:
ExecutorService service = Executors.newFixedThreadPool(10);
Reference: Java Multithreading & Concurrency
Q 43

What is the difference between shutdown(), shutdownNow(), and awaitTermination() in ExecutorService?

Medium
Answer
shutdown() stops accepting new tasks and allows previously submitted tasks to complete. shutdownNow() attempts to cancel actively running tasks via interruption and returns a list of unexecuted queued tasks. awaitTermination(timeout, unit) blocks the calling thread until all tasks finish after shutdown or timeout occurs.
Explanation
Standard graceful shutdown pattern combines all three: call shutdown(), await termination, and fallback to shutdownNow() if timeout expires.
Code Example Java
pool.shutdown(); // Refuse new tasks
try {
    if (!pool.awaitTermination(5, TimeUnit.SECONDS)) {
        pool.shutdownNow(); // Force interrupt active tasks
    }
} catch (InterruptedException e) {
    pool.shutdownNow();
}
Reference: Java Multithreading & Concurrency
Q 44

What is the difference between Thread.yield() and Thread.onSpinWait() in Java?

Hard
Answer
Thread.yield() hints to the OS thread scheduler that the current thread is willing to surrender CPU execution to other equal-priority threads. Thread.onSpinWait() (Java 9+) emits a specialized CPU PAUSE instruction inside busy-wait spin loops without releasing the OS thread, optimizing power consumption and memory bus contention.
Explanation
Use Thread.onSpinWait() inside spin-locks and CAS retry loops to instruct the CPU to avoid pipeline stalls and reduce power.
Code Example Java
volatile boolean eventFired = false;
// Spin-wait loop:
while (!eventFired) {
    Thread.onSpinWait(); // CPU PAUSE instruction hint
}
Reference: Java Multithreading & Concurrency
Q 45

What is the ABA Problem in lock-free concurrency and how does AtomicStampedReference solve it?

Hard
Answer
The ABA problem occurs in lock-free CAS algorithms when a value is changed from A to B, and then back to A. A thread executing CAS checks only the value, failing to detect that intermediate modifications occurred. AtomicStampedReference resolves this by pairing an integer version/stamp with the object reference.
Explanation
AtomicStampedReference updates both reference and integer version stamp atomically via compareAndSet(expectedRef, newRef, expectedStamp, newStamp).
Code Example Java
AtomicStampedReference<String> ref = new AtomicStampedReference<>("A", 0);
int stamp = ref.getStamp(); // 0
ref.compareAndSet("A", "B", stamp, stamp + 1); // Stamp becomes 1
ref.compareAndSet("B", "A", 1, 2);             // Stamp becomes 2
// Stale CAS expecting stamp 0 will safely FAIL even though value is 'A'!
Reference: Java Multithreading & Concurrency
Q 46

What is Exchanger in java.util.concurrent and what is its use case?

Medium
Answer
Exchanger is a synchronization point at which two threads can pair up and atomically swap data objects. Each thread presents an object to the exchange() method and receives the counterpart object from the other thread.
Explanation
Exchanger is frequently used in genetic algorithms, double-buffering systems, and pipeline architectures between producers and consumers.
Code Example Java
Exchanger<List<String>> exchanger = new Exchanger<>();
// Thread A and Thread B swap populated/empty buffer lists:
// List<String> fullBuffer = getFullBuffer();
// List<String> emptyBuffer = exchanger.exchange(fullBuffer);
Reference: Java Multithreading & Concurrency
Q 47

What is Phaser in java.util.concurrent and how does it improve upon CyclicBarrier and CountDownLatch?

Hard
Answer
Phaser is a flexible, reusable synchronization barrier that supports dynamic registration and deregistration of parties at runtime, multi-phase iterations, and hierarchical tree structures to reduce contention.
Explanation
Unlike CyclicBarrier where the number of parties is fixed at instantiation, Phaser allows threads to join (register()) or exit (arriveAndDeregister()) dynamically across phases.
Code Example Java
Phaser phaser = new Phaser(1); // Register main thread
for (int i = 0; i < 3; i++) {
    phaser.register(); // Dynamically register worker
    new Thread(() -> {
        System.out.println("Phase 1 task");
        phaser.arriveAndAwaitAdvance(); // Await phase completion
    }).start();
}
phaser.arriveAndDeregister(); // Deregister main thread
Reference: Java Multithreading & Concurrency
Q 48

What is the difference between execute() and submit() in ExecutorService?

Easy
Answer
execute() is defined in the Executor interface, accepts only Runnable, returns void, and lets unhandled runtime exceptions propagate to the uncaught exception handler. submit() is defined in ExecutorService, accepts both Runnable and Callable, returns a Future object, and swallows exceptions until Future.get() is called.
Explanation
Always check Future.get() or handle exceptions inside Callable/Runnable when using submit() so that worker failures are not silently missed.
Code Example Java
ExecutorService pool = Executors.newSingleThreadExecutor();
// execute: Fire-and-forget
pool.execute(() -> System.out.println("Executed"));

// submit: Returns Future for tracking status and exceptions
Future<String> future = pool.submit(() -> "Success");
System.out.println(future.get());
Reference: Java Multithreading & Concurrency
Q 49

What is False Sharing in multi-threaded CPU architectures and how does @Contended address it in Java?

Hard
Answer
False Sharing occurs when threads on different CPU cores modify distinct variables that reside on the same 64-byte CPU cache line, forcing continuous invalidation of the entire cache line across cores. The @jdk.internal.vm.annotation.Contended annotation adds memory padding to isolate fields onto separate cache lines.
Explanation
Classes like LongAdder and ConcurrentHashMap use internal cell arrays padded against false sharing to maximize parallel multi-core throughput.
Code Example Java
// Isolating variables to separate CPU cache lines via padding:
public class PaddedAtomicCounter {
    // @Contended (Internal JVM annotation)
    volatile long value1; // Cache line 1
    volatile long value2; // Cache line 2
}
Reference: Java Multithreading & Concurrency
Q 50

What is the difference between Thread starvation and Livelock in Java?

Medium
Answer
Starvation occurs when a thread is perpetually denied access to shared resources or CPU time due to greedy/high-priority threads. Livelock occurs when two or more threads continuously change their states in response to each other without making any actual functional progress.
Explanation
Unlike Deadlock where threads are blocked/waiting, threads in a Livelock are actively executing and consuming CPU, but repeatedly yielding or reacting to each other.
Code Example Java
// Livelock concept: Two polite threads continually giving way to each other
class Worker {
    private boolean active = true;
    public void work(Worker other) {
        while (active) {
            if (other.active) {
                // Yield resource back and forth infinitely
                Thread.yield();
                continue;
            }
            // Perform work
        }
    }
}
Reference: Java Multithreading & Concurrency
Q 51

What is Thread Interruption and how should an InterruptedException be properly handled in Java?

Medium
Answer
Thread interruption is a cooperative signaling mechanism requesting a thread to stop. Catching InterruptedException clears the thread's interrupted status. Code should either re-throw the InterruptedException or restore the interrupted flag by calling Thread.currentThread().interrupt(). Never swallow it silently.
Explanation
Swallowing InterruptedException hides the shutdown cancellation request from higher-level framework orchestrators and thread pools.
Code Example Java
public void runTask() {
    try {
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        // Proper handling: Restore interrupt flag so caller knows thread was interrupted
        Thread.currentThread().interrupt();
        System.err.println("Task interrupted cleanly");
    }
}
Reference: Java Multithreading & Concurrency
Q 52

What is Scoped Values (Java 21+) and why is it a superior alternative to ThreadLocal for Virtual Threads?

Hard
Answer
Scoped Values provide immutable, securely confined data sharing across threads within a bounded lexical execution scope. Unlike ThreadLocal (which has mutable state, high inheritance cost, and memory leak risks with millions of Virtual Threads), Scoped Values are lightweight, immutable, and automatically garbage-collected when the scope exits.
Explanation
Scoped Values are specifically optimized to share contextual data (e.g. security credentials, transaction IDs) across millions of virtual threads with negligible memory footprint.
Code Example Java
private static final ScopedValue<String> USER_ID = ScopedValue.newInstance();

ScopedValue.where(USER_ID, "usr_9981").run(() -> {
    System.out.println("Authenticated User: " + USER_ID.get());
});
Reference: Java Multithreading & Concurrency
Q 53

What is Structured Concurrency (Java 21+) and how does StructuredTaskScope improve multi-threaded error handling?

Hard
Answer
Structured Concurrency treats multiple concurrent subtasks running in separate threads as a single unit of work. StructuredTaskScope guarantees that subtasks complete or cancel together before the parent block exits, eliminating thread leaks, orphan tasks, and uncollected exceptions.
Explanation
StructuredTaskScope.ShutdownOnFailure cancels all remaining running subtasks immediately if any single subtask fails, saving CPU cycles.
Code Example Java
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
    Supplier<String> user = scope.fork(() -> fetchUser());
    Supplier<Integer> order = scope.fork(() -> fetchOrder());
    scope.join().throwIfFailed(); // Joins both subtasks
    System.out.println(user.get() + " : " + order.get());
}
Reference: Java Multithreading & Concurrency
Q 54

What are Virtual Threads (Project Loom, Java 21+) and how do they differ from Platform (OS) Threads?

Hard
Answer
Platform threads are 1:1 wrappers over heavyweight OS kernel threads (consuming ~1MB memory and expensive context switching). Virtual threads (Java 21+) are lightweight user-mode threads managed entirely by the JVM (consuming only a few hundred bytes), allowing applications to spawn millions of concurrent threads without exhausting OS resources.
Explanation
When a Virtual Thread executes a blocking I/O operation, the JVM automatically unmounts it from its carrier OS thread, allowing the carrier thread to execute other virtual tasks.
Code Example Java
// Spawning a lightweight Virtual Thread (Java 21+):
Thread.startVirtualThread(() -> {
    System.out.println("Virtual thread executing: " + Thread.currentThread());
});

// Virtual thread executor:
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    executor.submit(() -> System.out.println("Task in virtual thread"));
}
Reference: Java Multithreading & Concurrency
Q 55

What is the ForkJoinPool and the Work-Stealing Algorithm in Java Concurrency?

Hard
Answer
ForkJoinPool is an executor designed for divide-and-conquer parallel tasks (RecursiveTask/RecursiveAction). Each worker thread maintains its own double-ended queue (deque). When a worker finishes its tasks, it steals tasks from the tail of another busy worker thread's deque (Work-Stealing algorithm).
Explanation
Work-stealing maximizes CPU core utilization and prevents worker threads from idling while other threads have deep task backlogs. Parallel Streams use ForkJoinPool.commonPool() internally.
Code Example Java
ForkJoinPool pool = new ForkJoinPool();
// Parallel streams rely on common ForkJoinPool:
List<Integer> list = List.of(1, 2, 3, 4, 5);
list.parallelStream().forEach(n -> System.out.println(n + " : " + Thread.currentThread().getName()));
Reference: Java Multithreading & Concurrency
Q 56

What is CompletableFuture and how does it enable asynchronous reactive pipeline programming?

Hard
Answer
CompletableFuture (Java 8+) represents a composable, non-blocking asynchronous computation. It supports functional chaining (thenApply, thenAccept, thenCompose), error recovery (exceptionally, handle), and orchestrating multiple concurrent async pipelines (allOf, anyOf).
Explanation
Unlike legacy Future.get() which blocks the thread, CompletableFuture registers non-blocking callback stages executed by the ForkJoinPool.
Code Example Java
CompletableFuture.supplyAsync(() -> "Order #101")
    .thenApply(order -> order + " [Processed]")
    .thenAccept(System.out::println)
    .exceptionally(ex -> { System.err.println(ex.getMessage()); return null; });
Reference: Java Multithreading & Concurrency
Q 57

What is LongAdder and LongAccumulator and why are they faster than AtomicLong under high write contention?

Hard
Answer
AtomicLong suffers from CPU cache-line bouncing and spinning CAS retries when dozens of threads update the same memory location simultaneously. LongAdder maintains an internal cell array of variables that separate threads mutate independently, summing cell values only upon sum() calls.
Explanation
LongAdder eliminates thread contention by dynamically distributing updates across multiple internal cells, providing much higher write throughput.
Code Example Java
LongAdder adder = new LongAdder();
// High throughput parallel updates from multiple threads:
adder.increment();
adder.add(50);
long total = adder.sum(); // Aggregates cells when total count is required
Reference: Java Multithreading & Concurrency
Q 58

What are Atomic Variables (e.g. AtomicInteger, AtomicReference) and how does Compare-And-Swap (CAS) work?

Medium
Answer
Atomic classes in java.util.concurrent.atomic provide lock-free, thread-safe operations on single variables. They utilize CPU-level Compare-And-Swap (CAS) hardware instructions (atomic comparison of expected memory value with updated value) rather than heavyweight OS-level synchronization locks.
Explanation
CAS avoids thread suspension, context-switching overhead, and lock contention, achieving superior performance in lock-free algorithms.
Code Example Java
AtomicInteger counter = new AtomicInteger(0);
// Atomically increments and returns updated value via CAS:
int newVal = counter.incrementAndGet();
// Explicit CAS update:
counter.compareAndSet(1, 10); // Updates to 10 if current value is 1
Reference: Java Multithreading & Concurrency
Q 59

What is ThreadLocal in Java and what causes memory leaks when using it with thread pools?

Hard
Answer
ThreadLocal provides thread-confined variables where each thread has its own independent, isolated copy. In application servers using thread pools, worker threads are reused; if ThreadLocal.remove() is not called in a finally block, stale values persist and cause memory leaks through ClassLoader references.
Explanation
Always clean up ThreadLocal variables via threadLocal.remove() in a finally block when working in pooled thread environments.
Code Example Java
public class UserContext {
    private static final ThreadLocal<String> userHolder = new ThreadLocal<>();
    public static void set(String user) { userHolder.set(user); }
    public static String get() { return userHolder.get(); }
    public static void clear() { userHolder.remove(); } // Crucial cleanup!
}
Reference: Java Multithreading & Concurrency
Q 60

What is Semaphore and how is it used for resource throttling?

Medium
Answer
A Semaphore maintains a set of permits. Threads call acquire() to obtain a permit (blocking if none are available) and call release() to return permits upon completion. It is used to bound access to finite shared resources (e.g. rate limiters or DB connection pools).
Explanation
A Semaphore with 1 permit acts as a Binary Semaphore (similar to a non-reentrant mutex lock).
Code Example Java
Semaphore semaphore = new Semaphore(3); // Allows max 3 concurrent threads
Runnable task = () -> {
    try {
        semaphore.acquire();
        System.out.println("Accessing resource: " + Thread.currentThread().getName());
    } finally {
        semaphore.release();
    }
};
Reference: Java Multithreading & Concurrency

About This Topic

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