Concurrency
Round Robin Print — N Threads in Order
N threads must print a shared counter in strict round-robin order: thread 0 prints 1, thread 1 prints 2, …, wrapping back to thread 0, until maxCount is reached.
Problem#
N threads (numbered 0 to N-1) share a counter starting at 1. They must print counter values in strict round-robin order by thread id, up to maxCount:
T1: 1 → T2: 2 → T3: 3 → T1: 4 → T2: 5 → ... → T1: 10
Every thread must also stop cleanly once maxCount is exceeded — including threads whose turn never comes again.
Think Before Coding#
Work through these before looking at the solution:
-
How does a thread know it's its turn? The relationship: counter value v belongs to thread (v - 1) % N. Thread 0 prints 1, 4, 7, … (values where (v-1) % 3 == 0). Thread 1 prints 2, 5, 8, … and so on.
-
Why while around wait(), not if? Two reasons: (a) notifyAll() wakes every thread, so many threads wake up but only one has the right turn — the rest must re-check and go back to sleep; (b) wait() can return spuriously for OS-level reasons, so re-checking is always required.
-
How do all threads stop — including those whose turn never comes? The thread that pushes current past maxCount calls notifyAll() before returning. Every sleeping thread wakes, re-checks current > maxCount, and returns from its own loop. Without this final notifyAll(), threads parked waiting for turns that will never arrive sleep forever.
Implementation#
package RoundRobinPrint;
public class RoundRobinPrint {
private int current = 1; // next value to print
private final int totalThreads;
private final int maxCount;
private final Object lock = new Object();
RoundRobinPrint(int totalThreads, int maxCount) {
this.totalThreads = totalThreads;
this.maxCount = maxCount;
}
public void print(int threadId) throws InterruptedException {
while (true) {
synchronized (lock) {
// Wait while: still more to print AND it's not this thread's turn
while (current <= maxCount && (current - 1) % totalThreads != threadId)
lock.wait();
// Check termination first — current may have passed maxCount while sleeping
if (current > maxCount) {
lock.notifyAll(); // wake any remaining threads so they also exit
return;
}
// It's this thread's turn and we're still within range
System.out.println("T" + (threadId + 1) + ": " + current);
current++;
lock.notifyAll(); // wake all — the next thread needs to check its turn
}
}
}
}
Key Design Decisions#
Turn formula: (current - 1) % totalThreads == threadId
Counter value 1 → thread 0, value 2 → thread 1, …, value N → thread N-1, value N+1 → thread 0 again. Subtracting 1 before % ensures value 1 maps to thread 0 (not thread 1).
Two-part wait condition: counter limit AND turn check
The while combines both stopping conditions: current <= maxCount (don't exit early) AND (current-1) % totalThreads != threadId (not my turn yet). This means a thread wakes from its sleep only when one of those two conditions flips — either counting is done, or its turn arrived.
notifyAll() in the termination branch
When a thread exits because current > maxCount, it calls notifyAll() before returning. This is essential: other threads might still be parked in await(). Without it, they'd sleep forever waiting for turns that will never come (no one left to increment current and call notifyAll()).
notifyAll() not notify() in steady state
After printing and incrementing current, the thread calls notifyAll() to wake everyone. Only the thread whose turn it now is will pass the while check — the rest re-evaluate and go back to sleep. notify() would wake only one arbitrary thread, which may not be the right one.
Demo#
package RoundRobinPrint;
public class Demo {
public static void main(String[] args) throws InterruptedException {
int totalThreads = 3;
int maxCount = 10;
RoundRobinPrint rrp = new RoundRobinPrint(totalThreads, maxCount);
Thread[] threads = new Thread[totalThreads];
for (int i = 0; i < totalThreads; i++) {
final int id = i;
threads[i] = new Thread(() -> {
try { rrp.print(id); }
catch (InterruptedException e) { Thread.currentThread().interrupt(); }
});
}
// Start in reverse order — proves start order doesn't matter
for (int i = totalThreads - 1; i >= 0; i--) threads[i].start();
for (Thread t : threads) t.join();
}
}
Expected output (3 threads, maxCount = 10)#
T1: 1
T2: 2
T3: 3
T1: 4
T2: 5
T3: 6
T1: 7
T2: 8
T3: 9
T1: 10
Threads start in reverse order (T3, T2, T1) — the round-robin order is enforced by the shared counter and turn formula, not by thread start order.
Common Mistakes#
| Mistake | Why it breaks |
|---|---|
| if instead of while around wait() | notifyAll() wakes all threads; wrong ones proceed without re-checking their turn |
| notify() instead of notifyAll() | May wake the wrong thread; the one whose turn it is stays sleeping |
| No notifyAll() in the termination branch | Threads waiting for turns past maxCount sleep forever — program hangs |
| Turn formula current % totalThreads == threadId | Off by one: value 1 maps to thread 1, not thread 0 — wrong thread prints first |
| Checking current > maxCount with if (not re-checking after wakeup) | Thread could have been woken by notifyAll() at termination before its own increment — needs re-check inside while |