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.

August 10, 2026

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:

  1. 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.

  2. 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.

  3. 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#

java
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#

java
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#

MistakeWhy 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 branchThreads waiting for turns past maxCount sleep forever — program hangs
Turn formula current % totalThreads == threadIdOff 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