A classic thread coordination problem is making two independent threads print numbers in perfect sequential order: one printing odd numbers (1, 3, 5...) and the other printing even numbers (2, 4, 6...). Without proper signaling, the threads will print out of order. Java's BlockingQueue makes this coordination simple and robust.
In this guide, we will translate this concurrency problem into a simple relay-race analogy and trace the code's execution step by step.
Imagine a track relay race with two runners: Odd Runner and Even Runner. Instead of running laps, they are writing numbers in a notebook in order from 1 to 20.
They use a single **baton** (the ArrayBlockingQueue) to coordinate:
- Odd Runner starts the game. They write down their number
1in the notebook, print it, and then hand the baton to the Even Runner by placing the number `1` inside the queue. - Even Runner is waiting by the track. They cannot do anything until they receive the baton. As soon as the number `1` is placed in the queue, they take it out, add 1 to it to get
2, write down and print2, and wait again. - Meanwhile, Odd Runner increases their counter to
3and places it in the queue, passing the baton back.
By using the queue as a baton, they guarantee they never write out of turn!
Walkthrough of the Main Method Scenario
Let's trace how the program coordinates execution step-by-step from the entry point:
First, the main thread sets up the shared thread-safe queue with a limit of 20 elements:
BlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<>(20);
It then constructs the Odd and Even runner classes, sharing this single queue instance between them, and starts both threads.
The OddNumber thread runs its loop:
- It prints
"Odd Number 1"to the console. - It calls
blockingQueue.put(1), which places the number in the queue. - It increments its local counter:
number += 2(making it 3). - In the next iterations, if the queue is full (because the Even thread is slow),
blockingQueue.put(...)will automatically block and put the Odd thread to sleep, preventing it from printing ahead of time.
The EvenNumber thread runs its loop:
- It starts by calling
blockingQueue.take(). If the queue is empty, this call blocks, putting the Even thread to sleep. - As soon as the Odd thread puts
1in the queue, the Even thread wakes up and retrieves it. - It increments the value (1 + 1 = 2) and prints:
"Even Number 2". - If the printed value reaches 20, the loop breaks and the thread exits.
Java Implementation
Below is the complete Java code demonstrating odd/even printing using ArrayBlockingQueue:
package io.practise.accolite;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public class PrintOddEvenUsingTwoThreads {
public static void main(String[] args) {
BlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<>(20);
OddNumber oddNumber = new OddNumber(blockingQueue);
EvenNumber evenNumber = new EvenNumber(blockingQueue);
new Thread(oddNumber).start();
new Thread(evenNumber).start();
}
}
class OddNumber implements Runnable {
private BlockingQueue<Integer> blockingQueue;
static int number = 1;
public OddNumber(BlockingQueue<Integer> blockingQueue) {
this.blockingQueue = blockingQueue;
}
@Override
public void run() {
try {
for (int index = 0; index < 20; ++index) {
blockingQueue.put(number);
System.out.println("Odd Number " + number);
number += 2;
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
class EvenNumber implements Runnable {
int number = 2;
private BlockingQueue<Integer> blockingQueue;
public EvenNumber(BlockingQueue<Integer> blockingQueue) {
this.blockingQueue = blockingQueue;
}
@Override
public void run() {
try {
while (true) {
Integer take = blockingQueue.take();
take++;
System.out.println("Even Number " + take);
if (take == 20) {
break;
}
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
Conclusion
Using a BlockingQueue eliminates the need for low-level synchronized, wait, and notify blocks. The queue handles thread state blocking and thread-safe messaging internally, providing a clean, concurrent design that avoids race conditions and index-ordering bugs.