What is the difference between Thread.interrupt() and setting a custom volatile boolean cancel flag?
Hard
Answer
A custom volatile boolean flag can only be checked when the thread is actively executing CPU code; if the thread blocks in I/O, sleep(), or wait(), it will remain blocked forever. Thread.interrupt() wakes up blocked threads immediately by throwing InterruptedException, enabling prompt cancellation.
Explanation
Volatile flags cannot wake up sleeping or waiting threads. Thread interruption is the standard mechanism to cancel blocked threads.
Code Example
Java
// Flag fails if thread blocks in sleep:
// while(!flag) { Thread.sleep(10000); } // Won't wake up immediately!
// Interruption wakes up blocked sleep instantly:
Thread t = new Thread(() -> {
try { Thread.sleep(10000); } catch (InterruptedException e) {
System.out.println("Woke up immediately upon interrupt!");
}
});
t.start();
t.interrupt();
Reference:
Java Multithreading & Concurrency
Q 3
What is the difference between Callable and FutureTask in Java Concurrency?
Medium
Answer
Callable is a functional interface representing a task that returns a result. FutureTask is a concrete class that implements both Runnable and Future (RunnableFuture), wrapping a Callable so it can be passed directly to a Thread or executed manually.
Explanation
FutureTask can be started directly via new Thread(futureTask).start() or executed in a thread pool.
Code Example
Java
Callable<String> callable = () -> "Computation Complete";
FutureTask<String> futureTask = new FutureTask<>(callable);
Thread t = new Thread(futureTask);
t.start();
System.out.println(futureTask.get()); // Blocks and prints result
Reference:
Java Multithreading & Concurrency
Q 4
How does ThreadLocalRandom solve the contention bottleneck of java.util.Random?
Medium
Answer
java.util.Random shares a single AtomicLong internal seed across all threads, causing heavy CAS contention under concurrent multi-threaded usage. ThreadLocalRandom isolates random seed generation into the current thread's Thread instance, eliminating contention completely.
Explanation
Always use ThreadLocalRandom.current().nextInt() in concurrent environments instead of sharing a java.util.Random instance.
Code Example
Java
// Contention-free random number generation in multi-threaded code:
int randomNum = ThreadLocalRandom.current().nextInt(1, 100);
Reference:
Java Multithreading & Concurrency
Q 5
What is the difference between ThreadPriority and OS Thread Scheduling in Java?
Easy
Answer
Java thread priority (1 to 10 via setPriority()) serves only as a non-binding hint to the underlying OS scheduler. The JVM maps Java priorities to native OS priorities differently across platforms (Windows, Linux, macOS), and the OS is free to ignore them completely.
Explanation
Never rely on thread priorities for business logic correctness or execution order synchronization.
Code Example
Java
Thread t = new Thread(() -> System.out.println("High priority task"));
t.setPriority(Thread.MAX_PRIORITY); // Hint to OS scheduler (not guaranteed!)
t.start();
Reference:
Java Multithreading & Concurrency
Q 6
What is Lock Striping and how is it used in ConcurrentHashMap?
Hard
Answer
Lock Striping partitions a data structure into independent shards/stripes, guarding each stripe with an independent lock. In Java 8+ ConcurrentHashMap, lock striping is taken to the extreme: synchronization is applied on individual bucket head nodes using CAS and synchronized blocks rather than locking the entire map.
Explanation
Lock striping allows concurrent writes on different bucket keys without blocking each other.
Code Example
Java
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
// Thread 1 and Thread 2 write to different bucket nodes concurrently without lock contention
map.put("Key1", 100);
map.put("Key2", 200);
Reference:
Java Multithreading & Concurrency
Q 7
What is CopyOnWriteArraySet and how does it maintain thread safety?
Medium
Answer
CopyOnWriteArraySet is a thread-safe Set backed internally by a CopyOnWriteArrayList. All mutative operations (add, remove) create a fresh copy of the backing array and check for element uniqueness via array traversal.
Explanation
It is suited for small sets where read and iteration operations vastly outnumber modifications (e.g., event listener sets).
Code Example
Java
Set<String> listeners = new CopyOnWriteArraySet<>();
listeners.add("ListenerA");
// Fast lock-free iteration over snapshot without ConcurrentModificationException
for (String l : listeners) System.out.println(l);
Reference:
Java Multithreading & Concurrency
Q 8
What is the difference between AtomicReference and AtomicStampedReference?
Medium
Answer
AtomicReference provides atomic CAS operations on a single reference pointer. AtomicStampedReference pairs the reference pointer with an integer stamp to prevent the ABA problem by validating both pointer equality and stamp version equality simultaneously.
Explanation
AtomicReference cannot detect if a value was modified and restored back to the original reference between CAS checks.
Code Example
Java
AtomicReference<String> ref = new AtomicReference<>("initial");
ref.compareAndSet("initial", "updated");
AtomicStampedReference<String> stampedRef = new AtomicStampedReference<>("initial", 1);
stampedRef.compareAndSet("initial", "updated", 1, 2);
Reference:
Java Multithreading & Concurrency
Q 9
What is a Spinlock and when is Busy-Waiting acceptable in multi-threaded programming?
Hard
Answer
A Spinlock is a lock where a thread repeatedly checks a condition in a tight loop (busy-waiting) instead of sleeping. It is acceptable ONLY on multi-core systems when the expected lock hold duration is shorter than the time required to context-switch the thread (sub-microsecond operations).
Explanation
Always use Thread.onSpinWait() inside spin loops to optimize CPU cache pipeline efficiency.
Code Example
Java
class SimpleSpinLock {
private final AtomicBoolean locked = new AtomicBoolean(false);
public void lock() {
while (!locked.compareAndSet(false, true)) {
Thread.onSpinWait(); // CPU pause instruction hint
}
}
public void unlock() { locked.set(false); }
}
Reference:
Java Multithreading & Concurrency
Q 10
How does ThreadPoolExecutor handle uncaught exceptions thrown by worker threads?
Hard
Answer
If submitted via execute(), unhandled runtime exceptions are passed to the thread's UncaughtExceptionHandler and the worker thread terminates, causing the pool to spawn a replacement thread. If submitted via submit(), the exception is caught and stored internally, rethrown as ExecutionException when Future.get() is called.
Explanation
Overriding ThreadPoolExecutor.afterExecute(Runnable r, Throwable t) allows centralized logging of exceptions from both execute() and submit().
Code Example
Java
ThreadPoolExecutor pool = new ThreadPoolExecutor(2, 2, 0, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>()) {
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
if (t != null) System.err.println("Task failed with: " + t.getMessage());
}
};
Reference:
Java Multithreading & Concurrency
Q 11
What is the difference between Thread.stop(), Thread.suspend(), and Thread.resume() and why are they deprecated?
Easy
Answer
Thread.stop() forcibly terminates a thread, instantly releasing all held monitor locks and leaving shared data structures in inconsistent states. Thread.suspend() pauses a thread without releasing held locks, leading to guaranteed deadlocks if the resuming thread needs that lock. They are deprecated and disabled in modern Java.
Explanation
Cooperative interruption (Thread.interrupt()) and volatile cancellation flags are the only safe mechanisms to stop threads.
Code Example
Java
// Correct way to stop a thread:
class CleanTask implements Runnable {
private volatile boolean keepRunning = true;
public void stop() { keepRunning = false; }
public void run() {
while (keepRunning && !Thread.currentThread().isInterrupted()) {
// Work
}
}
}
Reference:
Java Multithreading & Concurrency
Q 12
What is a Reentrant Lock and what does reentrancy mean in Java?
Easy
Answer
Reentrancy means that if a thread already holds a lock on a monitor, it can re-enter any other synchronized block or lock section guarded by the exact same lock without deadlocking itself. The lock maintains a hold count that decrements on exit.
Explanation
Both Java's 'synchronized' keyword and 'ReentrantLock' are fully reentrant.
Code Example
Java
class Service {
synchronized void methodA() {
methodB(); // Reentrant: Same thread does not block on acquiring 'this' lock again
}
synchronized void methodB() {
System.out.println("Reentered successfully");
}
}
Reference:
Java Multithreading & Concurrency
Q 13
What is Thread Context Switching and why is it expensive?
Medium
Answer
Context switching is the process where the OS saves the CPU execution state (registers, program counter, stack pointer) of a preempted thread and restores the state of another scheduled thread. It is expensive due to CPU cycle overhead, cache invalidation, and TLB flushes.
Explanation
Virtual Threads (Java 21+) eliminate OS kernel context-switching by switching lightweight execution continuation frames directly in user-space inside the JVM.
Code Example
Java
// 10,000 OS threads cause severe context-switch thrashing.
// 10,000 Virtual threads run with near-zero context-switching cost:
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < 10000; i++) executor.submit(() -> Thread.sleep(100));
}
Reference:
Java Multithreading & Concurrency
Q 14
What is CompletableFuture.thenCompose() vs CompletableFuture.thenCombine()?
Hard
Answer
thenCompose() (flatMap) is used to chain dependent async computations where the second future depends on the result of the first. thenCombine() executes two independent futures in parallel and combines their results using a BiFunction when both complete.
Explanation
Use thenCompose for sequential async dependencies (Future A -> Future B) and thenCombine for parallel fan-out aggregation (Future A + Future B -> Result).
What is the Fork/Join commonPool and how is its parallelism level configured?
Hard
Answer
ForkJoinPool.commonPool() is the JVM-wide shared thread pool utilized by Parallel Streams and CompletableFuture async stages without custom executors. Its default parallelism is Runtime.getRuntime().availableProcessors() - 1, configurable via -Djava.util.concurrent.ForkJoinPool.common.parallelism=N.
Explanation
Blocking I/O operations should never be run inside the commonPool, as it can starve other parallel streams and async tasks across the entire JVM.
Code Example
Java
// Running compute tasks on common pool via parallel stream:
List<Integer> list = List.of(1, 2, 3, 4, 5);
list.parallelStream().map(n -> n * 2).forEach(System.out::println);
// Dedicated pool for blocking I/O:
ForkJoinPool customPool = new ForkJoinPool(8);
Reference:
Java Multithreading & Concurrency
Q 16
What is the difference between ConcurrentHashMap.computeIfAbsent() and putIfAbsent() in multi-threaded environments?
Medium
Answer
putIfAbsent() evaluates the value eagerly before insertion and can lead to wasted object creations. computeIfAbsent() executes the mapping lambda lazily and atomically only if the key is not already present, ensuring the mapping function runs exactly once per key.
Explanation
In ConcurrentHashMap, computeIfAbsent is locked on the specific bucket node, guaranteeing atomic initialization without duplicate computation.
Code Example
Java
ConcurrentMap<String, List<String>> map = new ConcurrentHashMap<>();
// Thread-safe atomic lazy creation of nested collection:
map.computeIfAbsent("users", k -> new CopyOnWriteArrayList<>()).add("Alice");
Reference:
Java Multithreading & Concurrency
Q 17
What is the difference between fair and unfair locking policies in ReentrantLock?
Medium
Answer
A fair lock (new ReentrantLock(true)) grants access strictly in FIFO order of thread requests, preventing starvation but reducing throughput due to frequent context switching. An unfair lock (default) allows a newly arriving thread to acquire the lock immediately if available (barging), yielding higher overall throughput.
Explanation
Unfair locks are default because the performance gain from avoiding thread context-switches usually outweighs theoretical starvation risks.
Code Example
Java
// Unfair lock (higher throughput, default):
ReentrantLock unfairLock = new ReentrantLock();
// Fair lock (FIFO order guaranteed, lower throughput):
ReentrantLock fairLock = new ReentrantLock(true);
Reference:
Java Multithreading & Concurrency
Q 18
What is the difference between Thread.interrupt(), isInterrupted(), and interrupted()?
Medium
Answer
interrupt() sets the target thread's interrupt flag. isInterrupted() is an instance method checking the thread's interrupt state without resetting it. interrupted() is a static method checking the current thread's state AND resetting the flag to false.
Explanation
If a thread is blocked in sleep() or wait() when interrupt() is called, it clears the flag and throws InterruptedException immediately.
Code Example
Java
Thread t = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
// Work loop checking status without clearing
}
});
t.start();
t.interrupt(); // Sets interrupt flag
Reference:
Java Multithreading & Concurrency
Q 19
What is Thread Contention and how can it be minimized in high-throughput systems?
Hard
Answer
Thread Contention occurs when multiple threads simultaneously compete for the exact same lock, forcing the JVM/OS to suspend and context-switch waiting threads. It is minimized by lock stripping, reducing lock holding scope, using CAS atomics, or thread-confinement (ThreadLocal).
Explanation
Techniques to reduce contention: 1. Fine-grained locks (ConcurrentHashMap). 2. Lock-free atomics (LongAdder). 3. ReadWriteLock for read-heavy flows.
Code Example
Java
// High contention bottleneck:
// synchronized void update() { ... long running operations ... }
// Minimized contention using Atomic / CAS:
private final AtomicInteger counter = new AtomicInteger();
void update() { counter.incrementAndGet(); }
Reference:
Java Multithreading & Concurrency
Q 20
What is the Producer-Consumer pattern and how is it implemented using BlockingQueue in Java?
Easy
Answer
The Producer-Consumer pattern decouples task creation from task processing using a shared bounded buffer. BlockingQueue handles all thread-safe synchronization automatically via blocking put() and take() methods.
Why is volatile long or double not inherently atomic on 32-bit JVM architectures?
Hard
Answer
In the Java Language Specification (JLS), 64-bit values (long and double) on 32-bit JVMs may be written as two separate 32-bit operations (word tearing). Declaring them volatile forces 64-bit writes to be atomic, but compound operations (like value++) still require AtomicLong.
Explanation
On modern 64-bit architectures, 64-bit aligned writes are typically atomic at the hardware level, but the JLS standard guarantees atomicity only when declared volatile.
Code Example
Java
// Guaranteeing atomic 64-bit read/write across all JVM architectures:
private volatile long timestamp;
// For atomic incrementation:
private final AtomicLong counter = new AtomicLong();
Reference:
Java Multithreading & Concurrency
Q 22
How do you detect and analyze Deadlocks in production Java applications?
Medium
Answer
Deadlocks can be detected via thread dumps using jcmd <pid> Thread.dump_to_file or jstack <pid>, programmatic detection using ThreadMXBean.findDeadlockedThreads(), or visual monitoring tools like VisualVM and Java Mission Control (JFR).
Explanation
ThreadMXBean allows runtime automated health check endpoints to detect deadlocks programmatically without restarting the JVM.
Code Example
Java
ThreadMXBean bean = ManagementFactory.getThreadMXBean();
long[] deadlockedIds = bean.findDeadlockedThreads();
if (deadlockedIds != null) {
ThreadInfo[] infos = bean.getThreadInfo(deadlockedIds);
for (ThreadInfo info : infos) System.err.println("Deadlocked: " + info.getThreadName());
}
Reference:
Java Multithreading & Concurrency
Q 23
What is the difference between SynchronousQueue, LinkedBlockingQueue, and ArrayBlockingQueue in thread pools?
Medium
Answer
ArrayBlockingQueue has a fixed array capacity and single shared lock. LinkedBlockingQueue can be bounded or unbounded with two separate locks for producers/consumers. SynchronousQueue has zero capacity, directly handing off tasks between threads (used in cached thread pools).
Explanation
Using an unbounded LinkedBlockingQueue with Executors.newFixedThreadPool means maximumPoolSize is ignored and rejected execution handlers are never triggered.
Code Example
Java
// Direct handoff without buffering:
BlockingQueue<Runnable> directHandOff = new SynchronousQueue<>();
// Separate put/take lock queue for high throughput:
BlockingQueue<Runnable> linkedQueue = new LinkedBlockingQueue<>(500);
Reference:
Java Multithreading & Concurrency
Q 24
What is AbstractQueuedSynchronizer (AQS) and how does it power the java.util.concurrent framework?
Hard
Answer
AQS is an abstract framework for building custom locks and synchronizers (ReentrantLock, Semaphore, CountDownLatch). It maintains an atomic volatile state variable (getState, setState, compareAndSetState) and a FIFO doubly-linked CLH queue of waiting threads managed via LockSupport.park().
Explanation
AQS supports two modes: Exclusive mode (one thread at a time, e.g., ReentrantLock) and Shared mode (multiple threads, e.g., CountDownLatch, Semaphore).
Code Example
Java
// Simplified custom Exclusive Lock using AQS helper Sync:
class Mutex {
private static class Sync extends AbstractQueuedSynchronizer {
protected boolean tryAcquire(int arg) { return compareAndSetState(0, 1); }
protected boolean tryRelease(int arg) { setState(0); return true; }
}
private final Sync sync = new Sync();
public void lock() { sync.acquire(1); }
public void unlock() { sync.release(1); }
}
Reference:
Java Multithreading & Concurrency
Q 25
What is the difference between Thread.sleep(), Object.wait(), and LockSupport.park()?
Hard
Answer
Thread.sleep() pauses the thread for a specified time without releasing locks. Object.wait() releases the monitor lock and requires a synchronized block. LockSupport.park() suspends the thread without needing a monitor lock and operates using a binary permit token (unpark).
Explanation
LockSupport.park() is the foundational building block used internally across AbstractQueuedSynchronizer (AQS), ReentrantLock, and Virtual Threads.
Code Example
Java
// LockSupport.park suspends until unpark(thread) is called
Thread worker = new Thread(() -> {
System.out.println("Parking thread...");
LockSupport.park(); // No synchronized block needed
System.out.println("Unparked and resumed!");
});
worker.start();
LockSupport.unpark(worker); // Grants permit to resume
Reference:
Java Multithreading & Concurrency
Q 26
What is the difference between ReentrantLock.lock() vs ReentrantLock.lockInterruptibly()?
Medium
Answer
lock() acquires the lock, blocking indefinitely and ignoring thread interruption until the lock is acquired. lockInterruptibly() acquires the lock, but if the current thread is interrupted while waiting for the lock, it immediately stops waiting and throws InterruptedException.
Explanation
lockInterruptibly() is essential for writing responsive cancelable tasks that avoid getting permanently stuck during lock acquisition.
Code Example
Java
ReentrantLock lock = new ReentrantLock();
try {
lock.lockInterruptibly(); // Can be cancelled if thread is interrupted while blocked
try { doWork(); } finally { lock.unlock(); }
} catch (InterruptedException e) {
System.err.println("Lock acquisition was interrupted");
}
Reference:
Java Multithreading & Concurrency
Q 27
What is ConcurrentLinkedQueue and how does it implement lock-free FIFO queue operations?
Hard
Answer
ConcurrentLinkedQueue is an unbounded thread-safe FIFO queue based on the Michael-Scott non-blocking lock-free queue algorithm, using atomic CAS instructions on volatile head and tail pointers.
Explanation
Because it never locks, size() is an O(n) traversal operation. Never call queue.size() in tight loop condition checks; use queue.isEmpty() instead.
Code Example
Java
Queue<String> queue = new ConcurrentLinkedQueue<>();
queue.offer("Task 1"); // Lock-free append
String task = queue.poll(); // Lock-free head extraction
Reference:
Java Multithreading & Concurrency
Q 28
What is Lock-Free vs Wait-Free concurrency?
Hard
Answer
Lock-Free guarantees system-wide progress (at least one thread makes progress at all times, though individual threads may experience CAS retry delays). Wait-Free is a stronger guarantee where every individual thread is guaranteed to make progress and complete its operation in a bounded number of steps.
Explanation
Wait-free algorithms eliminate thread starvation completely, but are significantly more complex to implement.
Code Example
Java
// Lock-free CAS loop (system-wide progress guaranteed):
do {
oldVal = atomic.get();
newVal = oldVal + 1;
} while (!atomic.compareAndSet(oldVal, newVal));
Reference:
Java Multithreading & Concurrency
Q 29
What is Thread.setDaemon(true) and what is the behavior of Daemon Threads?
Easy
Answer
A daemon thread is a low-priority background thread (e.g. Garbage Collector or heartbeats). The JVM halts and terminates automatically when all remaining running threads are daemon threads, immediately aborting remaining daemon thread execution without executing finally blocks.
Explanation
setDaemon(true) must be invoked before calling thread.start(), otherwise IllegalThreadStateException is thrown.
Code Example
Java
Thread daemon = new Thread(() -> {
while (true) { /* Background metrics collection */ }
});
daemon.setDaemon(true); // JVM will not wait for this thread on exit
daemon.start();
Reference:
Java Multithreading & Concurrency
Q 30
What is the Double-Checked Locking singleton anti-pattern if the volatile keyword is omitted?
Hard
Answer
Without 'volatile', the JVM and CPU can reorder instructions during object instantiation (assigning reference pointer to the variable before the object constructor completes). Another thread checking the outer null condition can observe a partially initialized object reference, causing critical runtime crashes.
Explanation
The volatile keyword prevents instruction reordering and guarantees that memory writes from constructor initialization are visible before assigning the instance pointer.
Code Example
Java
public class SafeSingleton {
private static volatile SafeSingleton instance; // Must be volatile!
public static SafeSingleton getInstance() {
if (instance == null) {
synchronized (SafeSingleton.class) {
if (instance == null) instance = new SafeSingleton();
}
}
return instance;
}
}
Reference:
Java Multithreading & Concurrency
About This Topic
Prepare for
Multithreading interviews with important concepts
and commonly asked questions.