Java Multithreaded Sequential Printing: Solutions Using Lock, wait/notify, Condition, Semaphore, and LockSupport
This article presents various Java solutions for common interview multithreading problems that require sequential printing, demonstrating implementations using Lock, wait/notify, Condition, Semaphore, and LockSupport, along with detailed code examples and explanations of thread communication mechanisms.
The article explains how to solve typical interview questions that require multiple threads to print characters or numbers in a specific order, focusing on Java concurrency primitives and providing complete code examples for each approach.
Using Lock : A ReentrantLock controls access to a shared state variable; each thread prints its character when state % 3 == targetNum . The implementation ensures correct ordering by incrementing state and releasing the lock.
public class PrintABCUsingLock {
private int times; // control print count
private int state; // current state to alternate threads
private Lock lock = new ReentrantLock();
public PrintABCUsingLock(int times) {
this.times = times;
}
private void printLetter(String name, int targetNum) {
for (int i = 0; i < times; ) {
lock.lock();
if (state % 3 == targetNum) {
state++;
i++;
System.out.print(name);
}
lock.unlock();
}
}
public static void main(String[] args) {
PrintABCUsingLock loopThread = new PrintABCUsingLock(1);
new Thread(() -> { loopThread.printLetter("B", 1); }, "B").start();
new Thread(() -> { loopThread.printLetter("A", 0); }, "A").start();
new Thread(() -> { loopThread.printLetter("C", 2); }, "C").start();
}
}Using wait/notify : Threads synchronize on a common monitor object; each thread waits until state % 3 matches its target, then prints and notifies all waiting threads.
public class PrintABCUsingWaitNotify {
private int state;
private int times;
private static final Object LOCK = new Object();
public PrintABCUsingWaitNotify(int times) { this.times = times; }
public static void main(String[] args) {
PrintABCUsingWaitNotify printABC = new PrintABCUsingWaitNotify(10);
new Thread(() -> { printABC.printLetter("A", 0); }, "A").start();
new Thread(() -> { printABC.printLetter("B", 1); }, "B").start();
new Thread(() -> { printABC.printLetter("C", 2); }, "C").start();
}
private void printLetter(String name, int targetState) {
for (int i = 0; i < times; i++) {
synchronized (LOCK) {
while (state % 3 != targetState) {
try { LOCK.wait(); } catch (InterruptedException e) { e.printStackTrace(); }
}
state++;
System.out.print(name);
LOCK.notifyAll();
}
}
}
}Using Condition : A ReentrantLock with three Condition objects replaces wait/notify . Each thread awaits its condition and signals the next, achieving precise wake‑ups.
public class PrintABCUsingLockCondition {
private int times;
private int state;
private static Lock lock = new ReentrantLock();
private static Condition c1 = lock.newCondition();
private static Condition c2 = lock.newCondition();
private static Condition c3 = lock.newCondition();
public PrintABCUsingLockCondition(int times) { this.times = times; }
public static void main(String[] args) {
PrintABCUsingLockCondition print = new PrintABCUsingLockCondition(10);
new Thread(() -> { print.printLetter("A", 0, c1, c2); }, "A").start();
new Thread(() -> { print.printLetter("B", 1, c2, c3); }, "B").start();
new Thread(() -> { print.printLetter("C", 2, c3, c1); }, "C").start();
}
private void printLetter(String name, int targetState, Condition current, Condition next) {
for (int i = 0; i < times; ) {
lock.lock();
try {
while (state % 3 != targetState) { current.await(); }
state++; i++; System.out.print(name); next.signal();
} catch (Exception e) { e.printStackTrace(); }
finally { lock.unlock(); }
}
}
}Using Semaphore (first problem) : Three semaphores form a token ring; only the thread whose semaphore is available proceeds, prints its character, then releases the next semaphore.
public class PrintABCUsingSemaphore {
private int times;
private static Semaphore semaphoreA = new Semaphore(1);
private static Semaphore semaphoreB = new Semaphore(0);
private static Semaphore semaphoreC = new Semaphore(0);
public PrintABCUsingSemaphore(int times) { this.times = times; }
public static void main(String[] args) {
PrintABCUsingSemaphore printer = new PrintABCUsingSemaphore(1);
new Thread(() -> { printer.print("A", semaphoreA, semaphoreB); }, "A").start();
new Thread(() -> { printer.print("B", semaphoreB, semaphoreC); }, "B").start();
new Thread(() -> { printer.print("C", semaphoreC, semaphoreA); }, "C").start();
}
private void print(String name, Semaphore current, Semaphore next) {
for (int i = 0; i < times; i++) {
try {
System.out.println("111" + Thread.currentThread().getName());
current.acquire();
System.out.print(name);
next.release();
System.out.println("222" + Thread.currentThread().getName());
} catch (InterruptedException e) { e.printStackTrace(); }
}
}
}Using Semaphore for N threads : An array of semaphores creates a circular chain; each thread acquires the previous semaphore, prints its index, then releases its own semaphore, enabling ordered printing of numbers from 0 to a limit.
public class LoopPrinter {
private final static int THREAD_COUNT = 3;
static int result = 0;
static int maxNum = 10;
public static void main(String[] args) throws InterruptedException {
final Semaphore[] semaphores = new Semaphore[THREAD_COUNT];
for (int i = 0; i < THREAD_COUNT; i++) {
semaphores[i] = new Semaphore(1);
if (i != THREAD_COUNT - 1) { semaphores[i].acquire(); }
}
for (int i = 0; i < THREAD_COUNT; i++) {
final Semaphore lastSemphore = i == 0 ? semaphores[THREAD_COUNT - 1] : semaphores[i - 1];
final Semaphore currentSemphore = semaphores[i];
final int index = i;
new Thread(() -> {
try {
while (true) {
lastSemphore.acquire();
System.out.println("thread" + index + ": " + result++);
if (result > maxNum) { System.exit(0); }
currentSemphore.release();
}
} catch (Exception e) { e.printStackTrace(); }
}).start();
}
}
}Using LockSupport : Threads park and unpark each other in a round‑robin fashion, eliminating the need for explicit lock objects.
public class PrintABCUsingLockSupport {
private static Thread threadA, threadB, threadC;
public static void main(String[] args) {
threadA = new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.print(Thread.currentThread().getName());
LockSupport.unpark(threadB);
LockSupport.park();
}
}, "A");
threadB = new Thread(() -> {
for (int i = 0; i < 10; i++) {
LockSupport.park();
System.out.print(Thread.currentThread().getName());
LockSupport.unpark(threadC);
}
}, "B");
threadC = new Thread(() -> {
for (int i = 0; i < 10; i++) {
LockSupport.park();
System.out.print(Thread.currentThread().getName());
LockSupport.unpark(threadA);
}
}, "C");
threadA.start();
threadB.start();
threadC.start();
}
}Number‑and‑Letter printing : Two additional examples show how to alternate printing numbers and letters using a shared lock and using LockSupport, illustrating the same coordination principles for different output patterns.
public class NumAndLetterPrinter {
private static Thread numThread, letterThread;
public static void main(String[] args) {
letterThread = new Thread(() -> {
for (int i = 0; i < 26; i++) {
System.out.print((char)('A' + i));
LockSupport.unpark(numThread);
LockSupport.park();
}
}, "letterThread");
numThread = new Thread(() -> {
for (int i = 1; i <= 26; i++) {
System.out.print(i);
LockSupport.park();
LockSupport.unpark(letterThread);
}
}, "numThread");
numThread.start();
letterThread.start();
}
}By practicing these patterns—Lock, wait/notify, Condition, Semaphore, and LockSupport—readers can strengthen their understanding of Java’s concurrency utilities and be well‑prepared for interview challenges that test thread‑communication skills.
Full-Stack Internet Architecture
Introducing full-stack Internet architecture technologies centered on Java
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.