THE MODN CHRONICLES

Interview Prep

Interview Questions on Multithreading — Synchronized, Volatile, ExecutorService, Deadlock & Producer-Consumer

Java multithreading is tested in every senior Java interview. It separates junior developers from senior ones. Every product company and senior service company role in India tests it — from Flipkart to Infosys Digital.

Java multithreading and concurrency concepts

Multithreading is the most feared Java interview topic — and the most rewarding to master.

Multithreading in Java Interviews

Multithreading is the most feared Java interview topic. It separates junior from senior developers. Every product company — Google, Amazon, Flipkart, Razorpay — and every senior service company role at TCS Digital, Infosys Power Programmer, and Wipro Turbo tests it.

The questions follow a predictable pattern: thread basics for 2-3 year experience, synchronization and concurrent collections for 4-5 years, and advanced concurrency patterns for 6+ years. If you can explain thread lifecycle, write synchronized code, and solve a producer-consumer problem on a whiteboard, you clear the round.

This guide covers 10 actual multithreading questions asked in Indian Java interviews — with code examples, diagrams, and the depth interviewers expect at each experience level.

“Explain the difference between synchronized and volatile” — this single question has decided more senior Java offers than any other.

Thread Basics

Q1: What is the difference between a process and a thread?

Process vs Thread:

Process:                        Thread:
┌─────────────────────┐        ┌─────────────────────┐
│  Own memory space    │        │  Shared memory (heap)│
│  Own heap & stack    │        │  Own stack only      │
│  Independent         │        │  Lightweight         │
│  IPC needed to talk  │        │  Direct communication│
│  Expensive to create │        │  Cheap to create     │
└─────────────────────┘        └─────────────────────┘

Process = independent program with its own memory.
Thread  = lightweight unit within a process sharing memory.

Multiple threads in one process:
┌──────────────────────────────────┐
│           Process (JVM)          │
│  ┌────────┐ ┌────────┐ ┌──────┐ │
│  │Thread 1│ │Thread 2│ │Thread│ │
│  │ Stack  │ │ Stack  │ │Stack │ │
│  └────────┘ └────────┘ └──────┘ │
│  ┌──────────────────────────────┐│
│  │     Shared Heap Memory       ││
│  └──────────────────────────────┘│
└──────────────────────────────────┘

Key: Threads share heap but have separate stacks.
This is why multithreading needs synchronization.

Q2: What are the states in a thread lifecycle?

Thread Lifecycle (6 states in Java):

  NEW ──start()──→ RUNNABLE ──scheduler──→ RUNNING
                      ↑                       │
                      │                       ├─sleep()──→ TIMED_WAITING
                      │                       ├─wait()───→ WAITING
                      │                       ├─lock──────→ BLOCKED
                      │                       │
                      ←──notify()/timeout──────┘
                                              │
                                         TERMINATED

States:
NEW            → Thread created, not yet started
RUNNABLE       → Ready to run, waiting for CPU
RUNNING        → Currently executing
BLOCKED        → Waiting to acquire a lock
WAITING        → Waiting indefinitely (wait(), join())
TIMED_WAITING  → Waiting with timeout (sleep(), wait(ms))
TERMINATED     → Execution complete

// Creating and starting a thread:
Thread t = new Thread(() -> {
    System.out.println("Running: " + Thread.currentThread().getName());
});
System.out.println(t.getState()); // NEW
t.start();
// State is now RUNNABLE → RUNNING
t.join(); // Wait for thread to finish
System.out.println(t.getState()); // TERMINATED

Q3: What is the difference between Runnable and Callable?

Runnable vs Callable:

Runnable:
- void run()           → no return value
- Cannot throw checked exceptions
- Available since Java 1.0

Callable:
- V call()             → returns a value via Future
- Can throw checked exceptions
- Available since Java 1.5

// Runnable — no return value
Runnable task = () -> {
    System.out.println("Processing...");
};
new Thread(task).start();

// Callable — returns a value
Callable<Integer> task = () -> {
    Thread.sleep(1000);
    return 42;
};

ExecutorService executor = Executors.newSingleThreadExecutor();
Future<Integer> future = executor.submit(task);

// future.get() blocks until result is ready
Integer result = future.get(); // 42
System.out.println("Result: " + result);

executor.shutdown();

// Use Runnable for fire-and-forget tasks
// Use Callable when you need a result or exception handling

Synchronization

Q4: What is synchronized? How does it work?

synchronized = intrinsic lock (monitor) mechanism.
Only one thread can hold the lock at a time.

// 1. Synchronized method — lock on 'this' object
public synchronized void increment() {
    count++; // only one thread at a time
}

// 2. Synchronized block — lock on specific object
public void increment() {
    synchronized (this) {
        count++;
    }
}

// 3. Static synchronized — lock on Class object
public static synchronized void increment() {
    count++; // lock on MyClass.class
}

// Equivalent to:
public static void increment() {
    synchronized (MyClass.class) {
        count++;
    }
}

// Key points:
// - synchronized method = coarse-grained (locks entire method)
// - synchronized block = fine-grained (locks only critical section)
// - Static synchronized = class-level lock (shared across instances)
// - Every object in Java has an intrinsic lock
// - Reentrant: same thread can acquire same lock multiple times

Q5: What is volatile? How is it different from synchronized?

volatile vs synchronized:

volatile:
- Guarantees VISIBILITY (reads from main memory, not CPU cache)
- Does NOT guarantee atomicity
- No locking, no blocking
- Use for: flags, booleans, simple state

synchronized:
- Guarantees both VISIBILITY and ATOMICITY
- Uses locking (can cause blocking)
- Use for: compound operations (check-then-act, read-modify-write)

// volatile example — flag to stop a thread
class Worker implements Runnable {
    private volatile boolean running = true;

    public void run() {
        while (running) {  // reads from main memory every time
            // do work
        }
    }

    public void stop() {
        running = false;  // visible to all threads immediately
    }
}

// Why volatile is NOT enough for count++:
// count++ is actually 3 operations:
// 1. Read count
// 2. Increment
// 3. Write count
// Two threads can read the same value before either writes.
// volatile ensures visibility but NOT atomicity of these 3 steps.

// For atomic increment, use:
// - synchronized
// - AtomicInteger.incrementAndGet()
// - Lock

Q6: What is a deadlock? How do you prevent it?

Deadlock = two threads waiting for each other's locks.
Neither can proceed. Program hangs forever.

// Classic deadlock example:
Object lockA = new Object();
Object lockB = new Object();

// Thread 1:
synchronized (lockA) {       // holds lockA
    Thread.sleep(100);
    synchronized (lockB) {   // waits for lockB (held by Thread 2)
        // never reached
    }
}

// Thread 2:
synchronized (lockB) {       // holds lockB
    Thread.sleep(100);
    synchronized (lockA) {   // waits for lockA (held by Thread 1)
        // never reached
    }
}

// Deadlock diagram:
// Thread 1 ──holds──→ lockA ←──waits── Thread 2
// Thread 1 ──waits──→ lockB ←──holds── Thread 2

// Prevention strategies:
// 1. Lock ordering — always acquire locks in the same order
synchronized (lockA) {
    synchronized (lockB) { /* safe */ }
}
// Both threads lock A first, then B. No circular wait.

// 2. tryLock with timeout (ReentrantLock)
if (lock.tryLock(1, TimeUnit.SECONDS)) {
    try { /* work */ } finally { lock.unlock(); }
}

// 3. Avoid nested locks when possible
// 4. Detection: jstack <pid> → shows thread dump with deadlocks

Concurrency API

Q7: What is ExecutorService? What are the thread pool types?

ExecutorService = framework for managing thread pools.
Why? Creating threads manually is expensive and uncontrolled.

// Thread pool types:

// 1. FixedThreadPool — fixed N threads
ExecutorService pool = Executors.newFixedThreadPool(5);
// 5 threads. Extra tasks wait in queue.
// Use for: known, steady workload

// 2. CachedThreadPool — creates threads as needed
ExecutorService pool = Executors.newCachedThreadPool();
// Creates new threads on demand, reuses idle ones (60s timeout)
// Use for: many short-lived tasks

// 3. SingleThreadExecutor — 1 thread
ExecutorService pool = Executors.newSingleThreadExecutor();
// Tasks execute sequentially, one at a time
// Use for: sequential task processing

// 4. ScheduledThreadPool — delayed/periodic tasks
ScheduledExecutorService pool = Executors.newScheduledThreadPool(3);
pool.scheduleAtFixedRate(() -> {
    System.out.println("Runs every 5 seconds");
}, 0, 5, TimeUnit.SECONDS);

// Usage pattern:
ExecutorService executor = Executors.newFixedThreadPool(5);
Future<String> future = executor.submit(() -> {
    return "Task completed";
});
String result = future.get(); // blocks until done
executor.shutdown();          // graceful shutdown

// Why thread pools > manual threads:
// - Reuses threads (no creation overhead)
// - Controls max concurrent threads
// - Provides task queue management
// - Graceful shutdown support

Q8: What are concurrent collections?

Concurrent collections = thread-safe collections
without locking the entire structure.

// 1. ConcurrentHashMap
// Segment-level locking (Java 8+: node-level CAS)
// Thread-safe without locking entire map
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("key", 1);
map.computeIfAbsent("key2", k -> 2);
// Use when: multiple threads read/write a shared map

// 2. CopyOnWriteArrayList
// Creates a new copy on every write
// Reads are lock-free (fast)
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
list.add("item"); // copies entire array
// Use when: read-heavy, write-rare (event listeners, config)

// 3. BlockingQueue
// Producer-consumer pattern
// put() blocks if full, take() blocks if empty
BlockingQueue<String> queue = new LinkedBlockingQueue<>(100);
queue.put("task");    // blocks if queue is full
queue.take();         // blocks if queue is empty
// Use when: producer-consumer, task queues

// 4. ConcurrentLinkedQueue
// Lock-free queue using CAS operations
// Non-blocking (offer/poll return immediately)
ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<>();
// Use when: high-throughput, non-blocking scenarios

// When to use each:
// HashMap → ConcurrentHashMap (general purpose)
// ArrayList → CopyOnWriteArrayList (read-heavy)
// LinkedList → BlockingQueue (producer-consumer)
// Queue → ConcurrentLinkedQueue (lock-free)
Developer working on concurrent Java applications

Concurrency questions separate mid-level from senior Java developers in every interview.

Practical Scenarios

Q9: Implement the Producer-Consumer pattern.

// Producer-Consumer using BlockingQueue
// BlockingQueue handles all synchronization automatically.

import java.util.concurrent.*;

public class ProducerConsumer {
    public static void main(String[] args) {
        BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(5);

        // Producer thread
        Thread producer = new Thread(() -> {
            try {
                for (int i = 1; i <= 10; i++) {
                    System.out.println("Producing: " + i);
                    queue.put(i);  // blocks if queue is full
                    Thread.sleep(100);
                }
                queue.put(-1); // poison pill to signal stop
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });

        // Consumer thread
        Thread consumer = new Thread(() -> {
            try {
                while (true) {
                    Integer item = queue.take(); // blocks if empty
                    if (item == -1) break;       // poison pill
                    System.out.println("Consuming: " + item);
                    Thread.sleep(200);
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });

        producer.start();
        consumer.start();
    }
}

// Why BlockingQueue > wait/notify:
// - No manual synchronization needed
// - No spurious wakeup handling
// - Built-in capacity management
// - Cleaner, less error-prone code
// - Multiple producer/consumer support out of the box

Q10: What is the difference between wait/notify and Lock/Condition?

wait/notify vs Lock/Condition:

wait/notify:
- Tied to synchronized keyword
- Object-level (one wait set per object)
- No fairness control
- Cannot interrupt a waiting thread easily

Lock/Condition:
- Uses ReentrantLock
- Multiple conditions per lock
- tryLock with timeout
- Fairness option (FIFO ordering)

// wait/notify example:
synchronized (lock) {
    while (!condition) {
        lock.wait();       // releases lock, waits
    }
    // do work
    lock.notifyAll();      // wake up waiting threads
}

// Lock/Condition example:
ReentrantLock lock = new ReentrantLock(true); // fair lock
Condition notEmpty = lock.newCondition();
Condition notFull = lock.newCondition();

lock.lock();
try {
    while (queue.isEmpty()) {
        notEmpty.await();   // wait for items
    }
    // consume item
    notFull.signal();       // signal producers
} finally {
    lock.unlock();
}

// When to use each:
// synchronized + wait/notify → simple scenarios
// Lock/Condition → complex scenarios:
//   - Multiple conditions (notEmpty, notFull)
//   - tryLock with timeout
//   - Fairness requirement
//   - Need to interrupt waiting threads

How to Prepare

Multithreading — Priority by Experience

2-3 Years

  • • Thread lifecycle & states
  • • synchronized keyword
  • • volatile keyword
  • • Runnable vs Callable
  • • Basic thread creation

4-5 Years

  • • ExecutorService & thread pools
  • • Concurrent collections
  • • Deadlock detection & prevention
  • • Producer-consumer pattern
  • • ReentrantLock & Condition

6+ Years

  • • Fork/Join framework
  • • CompletableFuture
  • • Lock-free algorithms (CAS)
  • • Thread dump analysis
  • • Performance tuning

Start Java Mock Interview

Get asked real Java multithreading interview questions — thread lifecycle, synchronized vs volatile, deadlock prevention, ExecutorService, and producer-consumer patterns.

Free · AI-powered feedback · Multithreading questions