In multithreading coordination, we often use wait/notify blocks or concurrency queues to print odd and even numbers sequentially. Another advanced approach is leveraging a thread pool manager: **ExecutorService**.

Using ExecutorService helps us decouple task creation from execution threads, managing threads dynamically from a pool.

Visualizing ExecutorService task coordination
Real-World Analogy: Kitchen Dispatcher and Two Chefs

Imagine a busy restaurant kitchen with a **head dispatcher (the ExecutorService)** and two chefs: Chef 1 (handles odd orders) and Chef 2 (handles even orders).

Instead of the chefs talking to each other directly, the dispatcher maintains a ticket track queue:

  • The dispatcher hands ticket `1` (Odd) to Chef 1.
  • Once Chef 1 finishes, the dispatcher hands ticket `2` (Even) to Chef 2.

By scheduling tickets sequentially through the dispatcher pool, the kitchen guarantees orders are plated in perfect order without chefs coordinating among themselves.

Java Implementation

By using a thread pool and submitting tasks, we can alternate printing task classes dynamically:

package io.practise.string;
 
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
public class TestEvenOddUsingExecutorService {
    private static final Object lock = new Object();
    private static int number = 1;
    private static final int MAX = 20;
 
    public static void main(String[] args) {
        // Create a fixed thread pool of 2 threads
        ExecutorService executor = Executors.newFixedThreadPool(2);
 
        executor.submit(() -> {
            while (number <= MAX) {
                synchronized (lock) {
                    if (number % 2 == 0) {
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            Thread.currentThread().interrupt();
                        }
                    } else {
                        System.out.println(Thread.currentThread().getName() + ": " + number++);
                        lock.notifyAll();
                    }
                }
            }
        });
 
        executor.submit(() -> {
            while (number <= MAX) {
                synchronized (lock) {
                    if (number % 2 != 0) {
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            Thread.currentThread().interrupt();
                        }
                    } else {
                        System.out.println(Thread.currentThread().getName() + ": " + number++);
                        lock.notifyAll();
                    }
                }
            }
        });
 
        executor.shutdown();
    }
}

Conclusion

Using `ExecutorService` lets you easily manage threads and submit tasks dynamically, ensuring that threads are automatically recycled when the application completes execution.