Java Multithreading - Thread Lifecycle, Runnable & Callable Interview Questions (2026) Interview Questions | JiQuest

add

#

Java Multithreading - Thread Lifecycle, Runnable & Callable Interview Questions (2026)

BACKEND INTERVIEW PREPARATION
Java Multithreading — Thread Lifecycle, Runnable & Callable
100+ interview questions on thread creation, lifecycle states, synchronization, daemon threads, thread priorities, inter-thread communication, and concurrent utilities.
⏳ 45 min read 📝 100+ Q&As 🎯 Core Java
⚡ Thread States Quick Reference
NEWThread created but start() not called
RUNNABLERunning or ready to run (waiting for CPU)
BLOCKEDWaiting to acquire a monitor lock
WAITINGwait(), join() without timeout, park()
TIMED_WAITINGsleep(t), wait(t), join(t), parkNanos(t)
TERMINATEDrun() completed or uncaught exception
Thread Lifecycle Flow
NEW
RUNNABLE
BLOCKED
RUNNABLE
TERMINATED
WAITING/TIMED_WAITING also transition back to RUNNABLE on notify/timeout

Thread Creation & Basics

Q1. What are the two main ways to create a thread in Java?

A: 1) Extend Thread class and override run(). 2) Implement Runnable interface and pass to Thread constructor. Runnable is preferred because: Java supports single inheritance (extending Thread blocks other extension), Runnable separates task from thread management, Runnable works with Executors and virtual threads.

// Method 1: Extend Thread
class MyThread extends Thread {
    public void run() { System.out.println("Thread running"); }
}
new MyThread().start();

// Method 2: Implement Runnable (preferred)
Runnable task = () -> System.out.println("Thread running");
new Thread(task).start();

Q2. What is the difference between start() and run()?

A: start() creates a new OS thread and schedules run() to execute in that thread — true parallelism. run() is a regular method call in the current thread — no new thread created. Calling run() directly is a common mistake; always call start() to spawn a thread.

Thread t = new Thread(() -> System.out.println(Thread.currentThread().getName()));
t.start(); // prints "Thread-0" (new thread)
t.run();   // prints "main" (current thread) — NOT a new thread!

Q3. What is Callable and how does it differ from Runnable?

A: Runnable: void run() — no return value, cannot throw checked exceptions. Callable<V>: V call() throws Exception — returns a value, can throw checked exceptions. Callable is used with ExecutorService.submit() which returns Future<V>.

Callable<Integer> task = () -> {
    Thread.sleep(100);
    return 42;
};
ExecutorService exec = Executors.newSingleThreadExecutor();
Future<Integer> future = exec.submit(task);
Integer result = future.get(); // blocks until result available

Q4. What are thread states in Java?

A: Java Thread.State enum has 6 states: NEW (created, not started), RUNNABLE (running or ready), BLOCKED (waiting for monitor lock — synchronized), WAITING (indefinite wait — wait(), join(), LockSupport.park()), TIMED_WAITING (wait with timeout — sleep(t), wait(t), join(t)), TERMINATED (run() completed).

Thread t = new Thread(() -> { /* task */ });
System.out.println(t.getState()); // NEW
t.start();
System.out.println(t.getState()); // RUNNABLE (or TERMINATED if very fast)

Q5. What is the difference between BLOCKED and WAITING states?

A: BLOCKED: thread is waiting to acquire a synchronized lock held by another thread — wakes up automatically when the lock is released. WAITING: thread called wait()/join()/park() — must be explicitly notified by notify()/notifyAll()/unpark() or join() target must finish. BLOCKED is lock-contention; WAITING is intentional suspension waiting for a condition.

Q6. What is Thread.sleep() and what state does it put the thread in?

A: Thread.sleep(millis) pauses current thread for at least millis milliseconds, putting it in TIMED_WAITING state. Releases CPU but does NOT release any held monitors/locks. Throws InterruptedException (checked). After sleep, thread returns to RUNNABLE (not guaranteed to run immediately).

try {
    Thread.sleep(1000); // sleep 1 second
} catch (InterruptedException e) {
    Thread.currentThread().interrupt(); // restore interrupt flag
}

Q7. What is Thread.join() and when do you use it?

A: join() makes the current thread wait until the target thread terminates. join(millis) waits up to millis milliseconds. Use when you need to ensure another thread completes before continuing. Puts current thread in WAITING or TIMED_WAITING state.

Thread worker = new Thread(() -> longRunningTask());
worker.start();
worker.join(); // main waits until worker finishes
System.out.println("Worker done, main continues");

Q8. What is Thread.yield() and is it reliable?

A: Thread.yield() is a hint to the scheduler that the current thread is willing to give up CPU time to other threads of the same priority. It's NOT reliable — the scheduler may ignore it entirely. Behavior is OS/JVM specific. Avoid using yield() in production; use proper synchronization or sleep() instead.

Q9. What is a daemon thread?

A: A daemon thread is a background thread that doesn't prevent the JVM from exiting. When all non-daemon (user) threads finish, JVM exits — daemon threads are killed abruptly. Set before start(): thread.setDaemon(true). Examples: GC thread, finalizer thread. Use for background tasks that should not block JVM shutdown (monitoring, cleanup).

Thread daemon = new Thread(() -> {
    while (true) { monitorSystem(); }
});
daemon.setDaemon(true);
daemon.start();
// JVM can exit even if daemon is running

Q10. What are thread priorities?

A: Java threads have priority 1 (MIN) to 10 (MAX), default 5 (NORM). Set with thread.setPriority(n). Higher priority threads get more CPU time — but scheduling is OS-dependent and not guaranteed. Don't rely on priorities for correctness; use them only as hints for performance tuning.

Q11. What is the Thread.currentThread() method?

A: Returns the Thread object for the currently executing thread. Use to get thread name, ID, state, or check if current thread is interrupted. Commonly used in logging and debugging.

Thread t = Thread.currentThread();
System.out.println(t.getName()); // "main" or "Thread-0" etc.
System.out.println(t.getId());   // unique long ID
System.out.println(t.isVirtual()); // Java 21: false for platform threads

Q12. What is thread interruption and how does it work?

A: Interruption is a cooperative mechanism to request a thread to stop. thread.interrupt() sets the interrupted flag. The thread checks this flag either: via blocking methods (sleep, wait, join) which throw InterruptedException — clearing the flag; or via Thread.interrupted() (static, clears flag) or Thread.currentThread().isInterrupted() (instance, does not clear flag). Always restore interrupt status after catching InterruptedException.

Thread worker = new Thread(() -> {
    while (!Thread.currentThread().isInterrupted()) {
        try {
            doWork();
            Thread.sleep(100);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt(); // restore flag
            break; // exit loop
        }
    }
});
worker.start();
worker.interrupt(); // request stop

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

A: Thread.interrupted() — static, returns interrupted status of CURRENT thread and CLEARS the flag. isInterrupted() — instance method, returns interrupted status without clearing the flag. Use isInterrupted() for checking; use interrupted() only when you deliberately want to clear the flag as part of the check.

Q14. What is the synchronized keyword?

A: synchronized ensures only one thread at a time executes a synchronized block or method on the same object monitor. Provides mutual exclusion (only one thread) and memory visibility (changes visible after synchronized block). Can be applied to: instance method (lock on this), static method (lock on Class object), block (lock on specified object).

class Counter {
    private int count = 0;
    public synchronized void increment() { count++; }     // lock on this
    public static synchronized void classMethod() { }     // lock on Counter.class
    public void method() {
        synchronized(this) { count++; }                   // synchronized block
    }
}

Q15. What is a race condition?

A: A race condition occurs when the correctness of a program depends on the relative timing or interleaving of multiple threads. Classic example: two threads reading and incrementing count — both read 0, both compute 1, both write 1 — final value is 1 instead of 2. Prevented by synchronization, atomic classes, or thread-safe data structures.

Q16. What is deadlock?

A: Deadlock occurs when two or more threads wait for each other's locks, creating a circular dependency — all involved threads block forever. Four conditions (Coffman): Mutual Exclusion, Hold and Wait, No Preemption, Circular Wait. Prevention: always acquire locks in the same order, use tryLock() with timeout, use lock ordering protocols.

// Classic deadlock:
// Thread 1: synchronized(lockA) { synchronized(lockB) { } }
// Thread 2: synchronized(lockB) { synchronized(lockA) { } }
// Prevention: always lock A before B in both threads

Q17. What is livelock?

A: Livelock is when threads actively respond to each other's state changes but make no progress. Unlike deadlock (threads blocked), livelocked threads keep running and changing state but never complete. Example: two people stepping aside to let each other pass but always stepping in the same direction. Rare in practice; solved by randomizing retry timing.

Q18. What is thread starvation?

A: Starvation occurs when a thread is perpetually denied CPU time or a lock because higher-priority threads or other threads always acquire the resource first. Can happen with synchronized (unfair locking) or with thread priorities. Fairness: use ReentrantLock(true) for fair locking — threads get lock in order of request.

Q19. What is the volatile keyword?

A: volatile ensures: 1) Memory visibility — all writes to a volatile variable are immediately visible to all threads (bypasses CPU caches). 2) Prevents instruction reordering around the volatile access. Does NOT provide atomicity for compound operations like i++ (still needs synchronization or AtomicInteger). Use for simple flag variables read by multiple threads.

class Service {
    private volatile boolean running = true;
    public void run() {
        while (running) { doWork(); }
    }
    public void stop() { running = false; } // volatile ensures visibility
}

Q20. What is the difference between volatile and synchronized?

A: volatile: visibility only — all threads see latest value; no mutual exclusion; no atomicity for compound ops. synchronized: mutual exclusion AND visibility — guarantees memory visibility like volatile plus atomic execution of the block. Use volatile for single writes/reads of a variable; synchronized for compound operations or when multiple variables must be updated atomically.

Q21. What is the Java Memory Model (JMM)?

A: JMM defines how threads interact through memory. Key concept: "happens-before" relationship — if A happens-before B, all writes by A are visible to B. Established by: synchronized blocks (unlock happens-before lock by another thread), volatile writes (happens-before subsequent read), thread.start() (happens-before anything in the thread), thread.join() (thread completion happens-before return from join).

Q22. What is wait() and notify() used for?

A: wait(), notify(), notifyAll() are Object methods for inter-thread communication (producer-consumer). Must be called within a synchronized block on the same object. wait() releases the lock and puts thread in WAITING state. notify() wakes one waiting thread. notifyAll() wakes all. After notify, awakened thread must re-acquire the lock.

class SharedResource {
    private boolean ready = false;
    synchronized void produce() {
        ready = true;
        notifyAll(); // wake waiting consumer
    }
    synchronized void consume() throws InterruptedException {
        while (!ready) wait(); // loop to guard against spurious wakeups
        // consume
    }
}

Q23. Why must wait() always be called in a loop?

A: Spurious wakeups — threads can wake up from wait() without notify() being called (JVM/OS artifact). Condition may still not be satisfied after waking. The loop re-checks the condition and calls wait() again if not ready. The "guard loop" pattern (while(!condition) wait()) is mandatory for correctness.

Q24. What is the difference between notify() and notifyAll()?

A: notify() wakes ONE arbitrary waiting thread — which thread is undefined, could be wrong consumer in complex scenarios. notifyAll() wakes ALL waiting threads — each re-checks condition, only eligible ones proceed. notifyAll() is safer but less efficient. Use notify() only when all waiting threads are identical and only one can/should proceed.

Q25. What is thread safety and how do you achieve it?

A: A class is thread-safe if it behaves correctly when accessed from multiple threads without external synchronization. Strategies: 1) Immutability — no mutable state (best). 2) Thread confinement — state confined to one thread (ThreadLocal). 3) Synchronization — synchronized, Lock. 4) Atomic variables — AtomicInteger, etc. 5) Thread-safe collections — ConcurrentHashMap, CopyOnWriteArrayList.

Q26. What is a ThreadLocal?

A: ThreadLocal provides a separate copy of a variable per thread. Each thread has its own initialized copy; changes in one thread don't affect others. Common uses: database connections, user session data, SimpleDateFormat instances. Must call remove() when done (in web servers, threads are pooled — ThreadLocal values persist across requests without cleanup).

ThreadLocal<String> userContext = new ThreadLocal<>();
userContext.set("user-rajesh");
String user = userContext.get(); // "user-rajesh" in this thread only
userContext.remove();            // cleanup — critical in thread pools

Q27. What is the danger of ThreadLocal in thread pools?

A: Thread pool threads are reused across requests. If ThreadLocal is set in request 1 and not removed, request 2 (on same thread) will see request 1's data — security and data leaks. Always call ThreadLocal.remove() in a finally block or use frameworks that manage cleanup (Spring uses RequestContextHolder which cleans up via DispatcherServlet).

Q28. What is a thread group?

A: ThreadGroup is a legacy mechanism (Java 1.0) to group threads for bulk operations like interrupt. Now obsolete — ExecutorService and virtual threads are the modern approach. ThreadGroup was used to propagate uncaught exceptions but Thread.setUncaughtExceptionHandler() is the modern equivalent.

Q29. What is UncaughtExceptionHandler?

A: If a thread throws an uncaught exception, UncaughtExceptionHandler is invoked. Set per thread: thread.setUncaughtExceptionHandler(handler). Or set default for all threads: Thread.setDefaultUncaughtExceptionHandler(handler). Use for logging, alerting, or cleanup when threads die unexpectedly.

Thread t = new Thread(() -> { throw new RuntimeException("crash"); });
t.setUncaughtExceptionHandler((thread, ex) ->
    System.err.println("Thread " + thread.getName() + " crashed: " + ex.getMessage())
);
t.start();

Q30. What is the difference between process and thread?

A: Process: independent execution unit with its own memory space (heap, stack, code), resources; communication via IPC (pipes, sockets). Thread: lightweight unit within a process sharing the process's memory/resources; communication via shared memory (faster but needs synchronization). Java runs as a process; each Java thread shares the JVM heap.

Q31. What happens when a thread throws an unchecked exception?

A: The thread terminates (TERMINATED state). Other threads are NOT affected — each thread has its own execution path. The UncaughtExceptionHandler is invoked. If no handler is set, stack trace is printed to System.err. The JVM continues running unless all non-daemon threads have terminated.

Q32. Can you call start() on a thread twice?

A: No — calling start() on a TERMINATED or already-started thread throws IllegalThreadStateException. Create a new Thread instance to run the task again. This is why Runnable (stateless, reusable) is preferred over Thread subclass.

Q33. What is the synchronized method vs synchronized block trade-off?

A: Synchronized method: entire method is critical section — simple but coarse-grained, reduces concurrency. Synchronized block: specify exactly which code and which lock — finer-grained, better throughput. Rule: synchronize as little as possible (minimize critical section size) to maximize concurrency.

// Coarse: entire method locked
public synchronized void update(Data data) {
    validate(data);       // doesn't need lock
    processCache(data);   // doesn't need lock
    writeToMap(data);     // needs lock
}
// Fine-grained:
public void update(Data data) {
    validate(data);
    processCache(data);
    synchronized(this) { writeToMap(data); }  // only lock what's needed
}

Q34. What is the double-checked locking pattern and is it safe?

A: A singleton initialization pattern. Safe ONLY with volatile since Java 5. Without volatile, a partially constructed object may be visible to other threads (instruction reordering). The volatile field ensures the assignment is visible after construction completes.

class Singleton {
    private static volatile Singleton instance;
    public static Singleton getInstance() {
        if (instance == null) {               // first check (no lock)
            synchronized (Singleton.class) {
                if (instance == null) {        // second check (with lock)
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

Q35. What is the initialization-on-demand holder pattern?

A: A lazy thread-safe singleton using JVM class loading guarantees. The holder class is loaded only when getInstance() is called — JVM guarantees class loading is thread-safe. No synchronization needed. Simpler and safer than double-checked locking.

class Singleton {
    private Singleton() {}
    private static class Holder {
        static final Singleton INSTANCE = new Singleton();
    }
    public static Singleton getInstance() { return Holder.INSTANCE; }
}

Q36. What is an AtomicInteger and when to use it?

A: AtomicInteger provides atomic operations on int without synchronization — uses CPU compare-and-swap (CAS) instruction. Faster than synchronized for simple counters. Key methods: get(), set(), getAndIncrement(), incrementAndGet(), compareAndSet(expected, update).

AtomicInteger counter = new AtomicInteger(0);
counter.incrementAndGet(); // atomic i++
counter.compareAndSet(1, 5); // if current==1, set to 5; returns true/false
// Multiple threads can safely call these without synchronized

Q37. What is the compare-and-swap (CAS) algorithm?

A: CAS is a hardware-level atomic operation: if current value == expected, update to new value (atomically). Returns success/failure. If failed, retry with fresh read — this is "optimistic locking". AtomicXxx classes, LongAdder, StampedLock use CAS. Advantage: no OS-level blocking, lower overhead than synchronized for low-contention scenarios.

Q38. What is a semaphore?

A: Semaphore(n) allows n threads to acquire simultaneously (n permits). acquire() decrements permit count (blocks if 0). release() increments permit. Used to limit concurrency: connection pool size, rate limiting, resource caps. Binary semaphore (n=1) can act as a mutex but unlike synchronized, can be released from a different thread.

Semaphore dbConnections = new Semaphore(10); // max 10 concurrent DB connections
dbConnections.acquire();
try {
    useDatabase();
} finally {
    dbConnections.release();
}

Q39. What is a CountDownLatch?

A: CountDownLatch(n) allows one or more threads to wait until n operations complete. countDown() decrements counter. await() blocks until counter reaches 0. One-shot — cannot be reused. Use: wait for N initialization tasks to complete, parallel task completion, gate opening.

CountDownLatch latch = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
    new Thread(() -> { doWork(); latch.countDown(); }).start();
}
latch.await(); // main waits until all 3 workers finish

Q40. What is a CyclicBarrier?

A: CyclicBarrier(n) makes n threads wait at a barrier point until all n arrive, then releases them all. Reusable (cyclic). Use: parallel computation phases — all threads must finish phase 1 before any starts phase 2. Optional Runnable runs when all threads arrive at barrier.

CyclicBarrier barrier = new CyclicBarrier(4, () -> System.out.println("All threads at barrier"));
for (int i = 0; i < 4; i++) {
    new Thread(() -> {
        doPhase1();
        barrier.await(); // wait for all 4
        doPhase2();      // all start phase2 together
    }).start();
}

Q41. What is the difference between CountDownLatch and CyclicBarrier?

FeatureCountDownLatchCyclicBarrier
ReusableNo (one-shot)Yes (cyclic)
Waiting threadsOne (or few) waits for NAll N wait for each other
Use caseWait for N tasks to start/finishSynchronize phases across N threads

Q42. What is a Phaser?

A: Phaser is a flexible barrier synchronizer (Java 7). Like CyclicBarrier but: variable number of parties (threads can register/deregister), multiple phases, supports hierarchical phasers for large-scale parallelism. arrive() (doesn't wait), arriveAndAwaitAdvance() (waits for all), arriveAndDeregister() (remove from future phases).

Q43. What is a Exchanger?

A: Exchanger<V> allows two threads to exchange objects at a rendezvous point. Both threads call exchange(item) — each blocks until the other arrives, then both receive the other's item. Use: pipeline stages passing data between producer and consumer thread.

Q44. What is thread starvation vs priority inversion?

A: Starvation: low-priority thread never gets CPU. Priority inversion: low-priority thread holds resource needed by high-priority thread — high-priority thread effectively runs at low priority. Classic example: Mars Pathfinder (1997) suffered priority inversion. Solutions: priority inheritance (OS-level), or just avoid priority-based designs.

Q45. How do you detect deadlock in Java?

A: 1) jstack <pid> — produces thread dump showing blocked threads and "Found Java-level deadlock" section. 2) JVisualVM / JConsole — Detect Deadlock button. 3) ThreadMXBean programmatically: ManagementFactory.getThreadMXBean().findDeadlockedThreads() returns deadlocked thread IDs. 4) JProfiler/YourKit — commercial profilers with deadlock detection.

ThreadMXBean bean = ManagementFactory.getThreadMXBean();
long[] ids = bean.findDeadlockedThreads();
if (ids != null) System.out.println("Deadlock detected: " + Arrays.toString(ids));

Q46. What is a thread dump and how do you analyze it?

A: A thread dump is a snapshot of all thread states. Generate with: jstack <pid>, kill -3 <pid> (Unix), or jcmd <pid> Thread.print. Look for: BLOCKED threads and what lock they're waiting on, WAITING threads (wait/join/park), CPU-heavy RUNNABLE threads for CPU spikes. Multiple dumps (3-5 seconds apart) reveal if threads are stuck or busy.

Q47. What is the Fork/Join framework?

A: Fork/Join (Java 7) is designed for divide-and-conquer parallel computation. ForkJoinPool uses work-stealing — idle threads steal tasks from busy threads' queues. RecursiveTask<V> (returns value) or RecursiveAction (no return). Base case: if task small enough, compute directly; else fork subtasks and join results.

class SumTask extends RecursiveTask<Long> {
    private final long[] arr;
    private final int start, end;
    protected Long compute() {
        if (end - start <= 1000) return directSum();
        int mid = (start + end) / 2;
        SumTask left = new SumTask(arr, start, mid);
        left.fork(); // async
        SumTask right = new SumTask(arr, mid, end);
        return right.compute() + left.join(); // compute right inline, wait for left
    }
}

Q48. What is a LockSupport?

A: LockSupport provides basic blocking primitives: park() blocks current thread, unpark(thread) unblocks a thread. Unlike wait/notify, unpark() can be called before park() (no race condition). Used internally by java.util.concurrent locks. Puts thread in WAITING state (park) or TIMED_WAITING (parkNanos/parkUntil).

Q49. What is the happens-before principle?

A: Happens-before is a formal guarantee in JMM that if action A happens-before action B, all writes by A are visible to B. Key happens-before rules: 1) Program order (within a thread). 2) Monitor unlock → subsequent lock. 3) volatile write → subsequent read. 4) Thread.start() → thread's actions. 5) Thread's actions → Thread.join() return. 6) Thread.interrupt() → detection of interrupt.

Q50. What is a reentrancy and what are reentrant locks?

A: Reentrancy means a thread can acquire a lock it already holds (without deadlocking itself). Java synchronized is reentrant — if a method calls another synchronized method on same object, it works fine. ReentrantLock is also reentrant. Non-reentrant locks would deadlock when a method calls another method that needs the same lock.

Q51. What is ReentrantLock and how does it differ from synchronized?

A: ReentrantLock (java.util.concurrent.locks): more features than synchronized. Key differences: tryLock() (attempt lock without blocking), lockInterruptibly() (abortable on interrupt), fair mode (FIFO waiting), multiple Condition objects per lock, timed lock acquisition. Downside: must manually unlock in finally block.

ReentrantLock lock = new ReentrantLock(true); // fair lock
lock.lock();
try {
    // critical section
} finally {
    lock.unlock(); // MUST unlock in finally
}
// tryLock with timeout:
if (lock.tryLock(100, TimeUnit.MILLISECONDS)) {
    try { /* ... */ } finally { lock.unlock(); }
}

Q52. What is ReadWriteLock?

A: ReadWriteLock (ReentrantReadWriteLock) allows multiple concurrent readers OR one writer (mutually exclusive). ReadLock: multiple threads can hold simultaneously (no writes happening). WriteLock: exclusive — no readers or writers. Excellent for read-heavy workloads (caches, configuration). writeLock.lock() waits for all readers to finish.

ReadWriteLock rwLock = new ReentrantReadWriteLock();
// Read operation:
rwLock.readLock().lock();
try { return map.get(key); } finally { rwLock.readLock().unlock(); }
// Write operation:
rwLock.writeLock().lock();
try { map.put(key, value); } finally { rwLock.writeLock().unlock(); }

Q53. What is a Condition object?

A: Condition (from Lock) is a modern replacement for Object.wait/notify. Lock.newCondition() creates a Condition tied to the lock. condition.await() releases lock and waits. condition.signal() wakes one waiting thread. Advantage: multiple conditions per lock (e.g., notEmpty and notFull for a bounded buffer).

Q54. What is StampedLock?

A: StampedLock (Java 8) is a more advanced lock with optimistic reading. Three modes: writeLock() (exclusive), readLock() (shared), tryOptimisticRead() (non-blocking — gets stamp, validate later). Optimistic read is ideal for short read operations where write conflicts are rare — no actual locking overhead unless a write happens.

StampedLock lock = new StampedLock();
long stamp = lock.tryOptimisticRead();
double x = this.x; // read without lock
double y = this.y;
if (!lock.validate(stamp)) { // check if write happened
    stamp = lock.readLock(); // fallback to real read lock
    try { x = this.x; y = this.y; } finally { lock.unlockRead(stamp); }
}

Q55. What is a thread-safe singleton using enum?

A: Enum singleton is the simplest, most robust singleton: inherently thread-safe (JVM guarantees single instance), serialization-safe (no deserialization attack), reflection-safe (cannot instantiate enum via reflection). Effective Java Item 3 recommendation.

public enum Singleton {
    INSTANCE;
    public void doWork() { /* ... */ }
}
Singleton.INSTANCE.doWork();

Q56. What is the memory visibility problem in Java?

A: CPUs cache memory in registers/L1/L2 cache. Without synchronization, one thread's writes may not be visible to another thread (it reads from cache). Demonstrated by a loop that never sees a flag change written by another thread. Solved by: volatile, synchronized, happens-before relationships, Atomic classes.

Q57. How do you implement a producer-consumer pattern?

// Using BlockingQueue (preferred):
BlockingQueue<String> queue = new ArrayBlockingQueue<>(100);
// Producer:
new Thread(() -> {
    while (true) queue.put(produce()); // blocks if full
}).start();
// Consumer:
new Thread(() -> {
    while (true) consume(queue.take()); // blocks if empty
}).start();

Q58. What is false sharing and how do you prevent it?

A: False sharing: multiple threads modify different variables that happen to be on the same CPU cache line (64 bytes). Thread A modifying variable X forces Thread B's cache of variable Y (same cache line) to invalidate — degrades performance. Prevention: @Contended annotation (Java 8) adds padding to put variables on separate cache lines. Common in high-performance lock-free algorithms.

Q59. What is a thread pool and why is it needed?

A: Thread pool maintains a pool of worker threads to execute tasks without creating a new thread per task. Benefits: reduces thread creation/destruction overhead (expensive OS operation), bounds total thread count (prevents resource exhaustion), reuses threads for multiple tasks. Managed via ExecutorService — covered in depth in the next post.

Q60. What is the difference between Callable and Future?

A: Callable is the task definition (returns value, can throw). Future is the handle to a pending result. future.get() blocks until result available. future.isDone() (non-blocking). future.cancel(true) attempts to cancel (interrupts if running). Future doesn't support callbacks; CompletableFuture (Java 8) adds reactive chaining.

Q61. What is the purpose of Thread.setName()?

A: Thread names appear in thread dumps and monitoring tools (jstack, JVisualVM). Meaningful names drastically improve debugging. Use descriptive names: "email-sender", "db-write-worker-1". In ExecutorService, use ThreadFactory to name threads: Executors.newFixedThreadPool(4, new ThreadFactory that names threads).

Q62. What is the object monitor in Java?

A: Every Java object has an associated monitor (mutex lock). synchronized methods/blocks acquire the object's monitor. Only one thread can hold the monitor at a time. When a thread holds the monitor, it can call wait() to release it and wait for notify(). Monitor = mutual exclusion + cooperation (wait/notify).

Q63. Can synchronized static methods deadlock with synchronized instance methods?

A: No — they use different locks. Synchronized static method locks on the Class object (Counter.class). Synchronized instance method locks on the instance (this). They don't contend. But two threads can call static and instance methods simultaneously without blocking each other, which may not be safe if they access shared state.

Q64. What is a blocking method vs non-blocking method?

A: Blocking method: may pause the calling thread indefinitely (I/O, queue.take(), lock.lock()). Non-blocking: returns immediately regardless (lock.tryLock(), queue.poll(), future.isDone()). Reactive and async programming aims to avoid blocking calls on thread pool threads. Virtual threads make blocking calls cheap again.

Q65. What happens if you call Thread.stop() or Thread.suspend()?

A: Thread.stop() is deprecated and removed (Java 18) — unsafe: throws ThreadDeath at arbitrary points, may leave objects in inconsistent state, doesn't release locks properly. Thread.suspend() is also deprecated — causes deadlocks if suspended thread holds a lock. Always use interrupt-based cooperative cancellation instead.

Q66. What is the ThreadFactory interface?

A: ThreadFactory creates threads for an ExecutorService. Implementing it lets you customize: thread name, daemon status, priority, UncaughtExceptionHandler. Use ThreadFactory to name threads for debugging. Java 21: Thread.ofVirtual().factory() creates virtual thread factory.

ThreadFactory factory = r -> {
    Thread t = new Thread(r, "worker-" + threadCount.incrementAndGet());
    t.setDaemon(true);
    return t;
};
ExecutorService exec = Executors.newFixedThreadPool(4, factory);

Q67. What is thread-local storage used for in web applications?

A: Web application thread-local patterns: 1) Request context (current user, locale, transaction). 2) Database connection/transaction binding (Spring's TransactionSynchronizationManager). 3) SimpleDateFormat instances (one per thread for thread safety). 4) MDC (Mapped Diagnostic Context) in logging frameworks — correlate log entries per request.

Q68. What is the cost of context switching?

A: Context switch: OS saves state of running thread, loads state of next thread — typically 1-10 microseconds. With thousands of threads, context switching overhead dominates. Thread pools limit threads to 2*CPU cores for CPU-bound work, or higher (100-200) for I/O-bound. Virtual threads (Java 21) use carrier thread pool — no OS context switch per virtual thread.

Q69. What is the ForkJoinPool.commonPool() and how is it used?

A: ForkJoinPool.commonPool() is a shared pool used by parallel streams and CompletableFuture by default. Size = number of available processors - 1. Tasks submitted to common pool should be non-blocking and short. Long-blocking tasks should use a custom pool to avoid starving common pool. Configure size with -Djava.util.concurrent.ForkJoinPool.common.parallelism=N.

Q70. What is the ABA problem in CAS-based algorithms?

A: ABA problem: Thread reads value A, another thread changes A→B→A. Thread's CAS succeeds (still sees A) but the value cycled through B — may cause incorrect behavior in lock-free data structures. Solution: versioned CAS — AtomicStampedReference or AtomicMarkableReference track a stamp/version alongside the value, detecting the cycle.

Q71. What is a spin lock?

A: Spin lock: a thread continuously checks (spins) if a lock is available instead of blocking. Pros: no context switch overhead — excellent for very short critical sections on multi-core systems. Cons: wastes CPU if lock held long. Java synchronized never spins indefinitely; it uses adaptive spinning briefly before blocking (JVM optimization).

Q72. How does Java handle thread safety for static fields?

A: Static fields are shared across all threads (single copy per class). They require the same synchronization as instance fields: synchronized static methods, static volatile fields, or static AtomicXxx. Static final fields are safely visible to all threads after class initialization (JMM guarantee for final fields).

Q73. What is a concurrent modification and ConcurrentModificationException?

A: ConcurrentModificationException occurs when a collection is structurally modified during iteration (even from a single thread using foreach + remove). Fix: Iterator.remove(), removeIf(), collect-then-delete, or use CopyOnWriteArrayList for concurrent scenarios. The exception is a best-effort detection (modCount check), not a hard safety guarantee.

List<String> list = new ArrayList<>(List.of("a","b","c"));
// Safe removal:
list.removeIf(s -> s.equals("b")); // OK
// Or:
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    if (it.next().equals("b")) it.remove(); // OK
}

Q74. What is the Runnable vs Thread design pattern?

A: Thread class mixes "what to run" (run method) with "how to run" (thread management). Runnable separates the two. Best practice: implement Runnable (or Callable), pass to Thread or ExecutorService. This follows the single responsibility principle and enables task reuse across different execution mechanisms (threads, pools, virtual threads).

Q75. What are the key interview topics for Java multithreading?

A: Most asked: 1) Thread states and lifecycle. 2) synchronized vs volatile. 3) Deadlock detection and prevention. 4) wait/notify producer-consumer. 5) AtomicInteger and CAS. 6) ThreadLocal memory leaks. 7) Double-checked locking. 8) CountDownLatch vs CyclicBarrier. 9) ReadWriteLock. 10) Thread pool sizing. 11) Java Memory Model. 12) Virtual threads (Java 21). Master these for 90% of concurrency interview coverage.

Q76. How do you implement a thread-safe counter?

// Option 1: synchronized
class Counter {
    private int count;
    public synchronized int increment() { return ++count; }
}
// Option 2: AtomicInteger (preferred for simple counters)
class Counter {
    private AtomicInteger count = new AtomicInteger(0);
    public int increment() { return count.incrementAndGet(); }
}
// Option 3: LongAdder (Java 8, best for high-contention counters)
class Counter {
    private LongAdder count = new LongAdder();
    public void increment() { count.increment(); }
    public long get() { return count.sum(); }
}

Q77. What is LongAdder and why is it better than AtomicLong for high contention?

A: AtomicLong uses a single CAS location — high contention causes many CAS failures and retries (threads spinning). LongAdder (Java 8) uses multiple cells; threads update different cells reducing contention, then sum() aggregates. For counters/statistics with many writers, LongAdder gives much better throughput. For single reader scenarios or when latest value is always needed, AtomicLong is fine.

Q78. What is the difference between synchronized(this) and synchronized(SomeClass.class)?

A: synchronized(this) uses the current instance as the monitor — only threads accessing the SAME instance are blocked. Different instances don't block each other. synchronized(SomeClass.class) uses the Class object — all threads using ANY instance of the class are blocked. Use class-level lock only for global state shared across all instances.

Q79. What are good thread pool sizes for different workloads?

A: CPU-bound: threads = N_cpu (or N_cpu + 1). I/O-bound: threads = N_cpu * (1 + wait_time/cpu_time) — more threads since most are waiting. Web server: historically 100-200 platform threads; with virtual threads (Java 21), use thread-per-request (virtually unlimited). Rule: profile and measure; don't guess.

Q80. How does virtual thread scheduling work in Java 21?

A: Virtual threads are mounted on platform carrier threads from a ForkJoinPool. When a virtual thread blocks on I/O or blocking API, it unmounts from carrier thread (carrier is free to run another virtual thread). When the blocking operation completes, the virtual thread is remounted on any available carrier. Stack frames are stored on heap (not OS stack). This enables millions of concurrent virtual threads with just a few carrier threads.

Q81. What is pinning in virtual threads?

A: A virtual thread is "pinned" to its carrier thread when it cannot be unmounted: inside synchronized blocks/methods, or when calling native methods. Pinned virtual threads hold the carrier thread even when blocked — reduces scalability advantage. Mitigation: replace synchronized with ReentrantLock in code that blocks inside synchronized. Java 21 prints pinning events with -Djdk.tracePinnedThreads=full.

Q82. What is InheritableThreadLocal?

A: InheritableThreadLocal: child threads inherit the parent thread's values at the time of child creation. If parent's ThreadLocal has "user=rajesh", child thread starts with same value. Changes in child don't affect parent. Note: with thread pools, "child" threads are created once and reused — they inherit from the pool creator, not from the submitting thread.

Q83. What is a memory barrier?

A: Memory barriers (fences) are CPU instructions that enforce ordering of memory operations. volatile write/read inserts appropriate barriers. synchronized also uses barriers (MonitorEnter/MonitorExit = full fence). Without barriers, CPUs and compilers can reorder reads and writes for performance — barriers prevent that.

Q84. How does AtomicReference work?

A: AtomicReference<V> provides atomic CAS operations on object references. compareAndSet(expected, update) atomically updates if current == expected. Used for lock-free data structures. AtomicReferenceArray, AtomicReferenceFieldUpdater provide similar for arrays and fields.

AtomicReference<String> ref = new AtomicReference<>("initial");
boolean success = ref.compareAndSet("initial", "updated");
String updated = ref.updateAndGet(s -> s.toUpperCase()); // Java 8

Q85. What is a monitor lock vs explicit lock?

A: Monitor lock (synchronized): built into JVM, automatically released on exit/exception, no try-finally needed. Explicit lock (ReentrantLock): more control (tryLock, timed lock, fair mode, multiple conditions), MUST be released in finally. Use synchronized for simple cases; ReentrantLock for advanced scenarios requiring tryLock, fairness, or multiple conditions.

Q86. What is the difference between sequential consistency and linearizability?

A: Sequential consistency: all operations appear in some sequential order consistent with each thread's program order, but not necessarily real-time order. Linearizability: each operation appears to take effect instantaneously at some point between its start and end — stronger, includes real-time ordering. Java's volatile and AtomicXxx provide sequential consistency; full linearizability needs synchronized or CAS with barriers.

Q87. How do you test concurrent code?

A: Challenges: races are timing-dependent; unit tests may not expose them. Strategies: 1) jcstress framework — designed for concurrent correctness tests. 2) Stress testing with many threads. 3) Code review for known patterns (check-then-act, read-modify-write without sync). 4) Static analysis tools (SpotBugs, FindBugs). 5) Thread sanitizers. 6) Random sleep injection to expose timing windows. 7) Formal verification for critical sections.

Q88. What is the executor shutdown sequence?

ExecutorService executor = Executors.newFixedThreadPool(4);
// Graceful shutdown:
executor.shutdown(); // stops accepting new tasks
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
    executor.shutdownNow(); // cancel running tasks (interrupts)
    if (!executor.awaitTermination(60, TimeUnit.SECONDS))
        System.err.println("Executor did not terminate");
}

Q89. What is the difference between shutdown() and shutdownNow()?

A: shutdown(): no new tasks accepted; previously submitted tasks (queued + running) complete normally. shutdownNow(): attempts to cancel running tasks (interrupts threads), returns list of queued tasks not started. Neither waits for completion — use awaitTermination() after.

Q90. What is optimistic vs pessimistic locking?

A: Pessimistic: assume conflict will happen — always lock before accessing (synchronized, ReentrantLock). Overhead even if no contention. Optimistic: assume no conflict — read without lock, validate before commit; retry if conflict (CAS-based, StampedLock optimistic read, database optimistic locking with version column). Optimistic is better for low-contention; pessimistic for high-contention.

Q91. What causes OutOfMemoryError related to threads?

A: "Unable to create new native thread" — OS limit on threads exceeded (typically 1000-10000 on Linux). Each platform thread needs OS resources + stack space (~512KB-1MB). With virtual threads (Java 21), you can create millions without this issue. Fix: use thread pool instead of unbounded thread creation, increase OS limits (ulimit -u), or migrate to virtual threads.

Q92. What is a thread-safe lazy initialization pattern?

A: Three safe patterns: 1) static inner holder class (simplest). 2) Double-checked locking with volatile. 3) Enum singleton. Unsafe pattern: if-null check without synchronization — race condition. synchronized method: safe but blocks all callers including after initialization (unnecessary overhead).

Q93. How does the JVM optimize synchronized blocks?

A: JVM optimizations for synchronized: 1) Biased locking — if only one thread ever locks an object, bias toward that thread (no CAS needed). 2) Lock elision — JIT removes sync on objects provably thread-local (escape analysis). 3) Lock coarsening — merges adjacent sync blocks. 4) Adaptive spinning — spins briefly before blocking. Java 15 deprecated biased locking (removed Java 18) as virtual threads make it less relevant.

Q94. What is a completable future vs future?

A: Future: blocking (get() waits), no callbacks, no composition. CompletableFuture: non-blocking via callbacks (thenApply, thenAccept), composable (thenCompose, thenCombine), exception handling (exceptionally, handle), can be manually completed. CompletableFuture is the modern async programming API in Java 8+. Covered in depth in the next post on Executors.

Q95. What does it mean that the String class is thread-safe?

A: String is immutable — once created, its value cannot change. Immutable objects are inherently thread-safe: no synchronization needed, any number of threads can access a String simultaneously. This is why String is preferred for map keys, session IDs, and shared configuration. Compare with StringBuilder (mutable, NOT thread-safe) vs StringBuffer (mutable, thread-safe via synchronized methods).

Q96. What is the volatile double-checked singleton pitfall before Java 5?

A: Before Java 5 (pre-JMM revision), volatile didn't fully prevent reordering — even with volatile, the JVM could publish a partially-constructed object. Java 5 fixed the JMM: volatile fields now have acquire/release semantics ensuring the constructed object is fully visible before the reference is published. Double-checked locking is only safe in Java 5+.

Q97. What is a thread-per-request model vs thread-pool model?

A: Thread-per-request: create a new thread per incoming request (simple, great isolation). Doesn't scale beyond a few thousand concurrent requests (OS thread limit, memory). Thread-pool model: fixed/bounded pool of worker threads. Better resource utilization; concurrent requests queued. Virtual threads (Java 21) make thread-per-request viable again at millions of scale.

Q98. What is a Semaphore vs synchronized?

A: synchronized: exactly one thread at a time. Semaphore(n): up to n threads simultaneously. Semaphore can also be released by a different thread than the one that acquired (unlike synchronized which can only be released by the holder). Semaphore is more general; synchronized is simpler for mutual exclusion.

Q99. What are thread-safe collections vs synchronized collections?

A: Synchronized collections (Collections.synchronizedList, synchronizedMap): each method synchronized — but compound operations (if-contains-then-add) still need external sync; also, iteration must be externally synchronized. ConcurrentHashMap, CopyOnWriteArrayList: purpose-built concurrent collections with better throughput (see next post). Prefer concurrent collections over synchronized wrappers for new code.

Q100. What is the impact of synchronized on performance?

A: Uncontended synchronized (only one thread) is very fast (~10ns, JIT can optimize/elide). Contended synchronized: thread blocking/unblocking involves OS system call — expensive (~1-10 microseconds). High contention causes "lock convoy" — threads queued for lock, throughput collapses. Mitigation: minimize lock duration, use striped locks (ConcurrentHashMap segments), use lock-free algorithms (Atomic classes), or reduce sharing.

Q101. What is the best practice for exception handling in threads?

A: Never swallow exceptions in thread run() methods. Options: 1) Log and continue for non-fatal errors. 2) Set Thread.UncaughtExceptionHandler for unexpected errors. 3) Use ExecutorService — exceptions in Callable are captured in Future; call future.get() to see them. 4) Always restore interrupt status if catching InterruptedException (never just log and ignore).

No comments
Leave a Comment