Thread questions
Here are 3 classic, frequently asked Java multithreading coding problems ranging from intermediate to advanced difficulty. Each includes the core concept being tested, a step-by-step breakdown, and production-ready Java solutions using modern java.util.concurrent primitives.
Problem 1: Print Even and Odd Numbers Alternately using Two Threads¶
Concept Tested¶
Thread synchronization, inter-thread communication using wait() and notify(), or coordination using concurrency constructs like ReentrantLock and Condition.
Problem Statement¶
Write a program with two threads. Thread 1 should print odd numbers and Thread 2 should print even numbers sequentially up to a given limit $N$ (e.g., 1, 2, 3, 4, ... N).
Step-by-Step Approach¶
- Maintain a shared counter variable initialized to
1and a maximum limit $N$. - Create two worker threads sharing a single monitor object (or lock).
- Odd Thread Logic: In a loop, lock the monitor. If the counter is even, call
wait()to release the lock and pause execution. Once notified and counter becomes odd, print the number, increment the counter, and callnotify()to wake up the Even thread. - Even Thread Logic: Follow the inverse condition—wait if the counter is odd; print, increment, and notify if the counter is even.
Solution¶
public class EvenOddPrinter {
private final int limit;
private int counter = 1;
public EvenOddPrinter(int limit) {
this.limit = limit;
}
public synchronized void printOdd() {
while (counter <= limit) {
// Wait while it is the turn for an even number
while (counter % 2 == 0) {
try {
wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
if (counter <= limit) {
System.out.println(Thread.currentThread().getName() + ": " + counter);
counter++;
notify(); // Notify the even thread
}
}
}
public synchronized void printEven() {
while (counter <= limit) {
// Wait while it is the turn for an odd number
while (counter % 2 != 0) {
try {
wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
if (counter <= limit) {
System.out.println(Thread.currentThread().getName() + ": " + counter);
counter++;
notify(); // Notify the odd thread
}
}
}
public static void main(String[] args) {
EvenOddPrinter printer = new EvenOddPrinter(10);
Thread oddThread = new Thread(printer::printOdd, "Odd-Thread");
Thread evenThread = new Thread(printer::printEven, "Even-Thread");
oddThread.start();
evenThread.start();
}
}
Problem 2: Implement a Thread-Safe Bounded Blocking Queue (Producer-Consumer)¶
Concept Tested¶
Custom data structure concurrency, handling buffer overflow/underflow, preventing race conditions, and choosing explicit locks (ReentrantLock + Condition) over raw synchronized blocks.
Problem Statement¶
Implement a fixed-capacity FIFO Blocking Queue from scratch with enqueue(item) and dequeue() methods without using Java’s built-in BlockingQueue classes.
Step-by-Step Approach¶
- Use an internal array or linked list along with pointers for head, tail, and current size.
- Use a
ReentrantLockto protect queue mutations. - Define two separate
Conditionobjects on the lock:notFullandnotEmpty. - Enqueue: Acquire the lock. If
size == capacity, await onnotFull. Put the item at the tail, increment size, and signalnotEmpty. - Dequeue: Acquire the lock. If
size == 0, await onnotEmpty. Remove the item at the head, decrement size, and signalnotFull.
Solution¶
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class BoundedBlockingQueue<T> {
private final Object[] items;
private int head = 0;
private int tail = 0;
private int count = 0;
private final Lock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();
public BoundedBlockingQueue(int capacity) {
if (capacity <= 0) {
throw new IllegalArgumentException("Capacity must be positive");
}
this.items = new Object[capacity];
}
public void enqueue(T item) throws InterruptedException {
lock.lock();
try {
// Guard against spurious wakeups with a while loop
while (count == items.length) {
notFull.await(); // Wait until queue has space
}
items[tail] = item;
tail = (tail + 1) % items.length; // Circular index bump
count++;
notEmpty.signal(); // Signal consumer that an item is available
} finally {
lock.unlock();
}
}
@SuppressWarnings("unchecked")
public T dequeue() throws InterruptedException {
lock.lock();
try {
while (count == 0) {
notEmpty.await(); // Wait until queue has an item
}
T item = (T) items[head];
items[head] = null; // Help garbage collection
head = (head + 1) % items.length; // Circular index bump
count--;
notFull.signal(); // Signal producer that space is freed
return item;
} finally {
lock.unlock();
}
}
}
Problem 3: Multi-Threaded Task Coordinator (Run N Tasks in Order across 3 Threads)¶
Concept Tested¶
Fine-grained state tracking, sequence generation across multiple threads (Thread A $\rightarrow$ Thread B $\rightarrow$ Thread C $\rightarrow$ Thread A), and condition signaling.
Problem Statement¶
Print the string sequence "ABCABCABC..." $K$ times using three distinct threads: ThreadA (prints 'A'), ThreadB (prints 'B'), and ThreadC (prints 'C').
Step-by-Step Approach¶
- Track the turn state using an integer
statewhere $0 \rightarrow \text{'A'}$, $1 \rightarrow \text{'B'}$, and $2 \rightarrow \text{'C'}$. - Share a monitor lock across all three threads.
- Pass target state ($0, 1,$ or $2$) and character to print to a unified task implementation.
- While the loop count hasn't reached $K$, check if it's the thread's turn. If not, wait. If yes, print the character, update state to
(state + 1) % 3, and callnotifyAll()so the next waiting thread can run.
Solution¶
public class SequencePrinter {
private final int totalRounds;
private int state = 0; // 0 for A, 1 for B, 2 for C
public SequencePrinter(int totalRounds) {
this.totalRounds = totalRounds;
}
public synchronized void printChar(char ch, int targetState) {
for (int i = 0; i < totalRounds; i++) {
while (state != targetState) {
try {
wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
System.out.print(ch);
state = (state + 1) % 3; // Advance to next target state
notifyAll(); // Notify other threads checking state condition
}
}
public static void main(String[] args) {
int rounds = 5;
SequencePrinter printer = new SequencePrinter(rounds);
Thread t1 = new Thread(() -> printer.printChar('A', 0), "Thread-A");
Thread t2 = new Thread(() -> printer.printChar('B', 1), "Thread-B");
Thread t3 = new Thread(() -> printer.printChar('C', 2), "Thread-C");
t1.start();
t2.start();
t3.start();
}
}
Key Takeaways for Multithreading Coding Interviews¶
- Always use
whileloops instead ofifforwait()/await(): This protects your implementation from spurious wakeups and state shifts between signal and wakeup. - Always unlock in
finallyblocks: When using explicitLockobjects,lock.unlock()must always reside in afinallyblock to prevent deadlocks when exceptions occur. - Minimize lock scope: Perform expensive operations (like network calls or payload construction) outside locked blocks.