Printing Even and Odd Numbers with Two Threads in Java: An In-Depth Analysis from Problem to Solution

Dec 03, 2025 · Programming · 11 views · 7.8

Keywords: Java Multithreading | Synchronization Mechanism | Even-Odd Printing

Abstract: This article delves into the classic problem of printing even and odd numbers sequentially using Java multithreading synchronization mechanisms. By analyzing logical flaws in the original code, it explains core principles of inter-thread communication, synchronization locks, and wait/notify mechanisms. Based on the best solution, the article restructures the code to demonstrate precise alternating output through shared state variables and conditional waiting. It also compares other implementation approaches, offering comprehensive guidance for multithreaded programming practices.

In Java multithreaded programming, achieving alternating printing of even and odd numbers with two threads is a classic synchronization problem involving core concepts such as inter-thread communication, shared resource management, and conditional waiting. This article explores its implementation principles step by step, from problem analysis to solution, through a concrete case study.

Problem Analysis: Flaws in the Original Code

The original code attempts to alternate printing even and odd numbers using two threads and a shared Printer object. However, several key issues exist:

These defects lead to output that does not match the expected alternating sequence, such as duplicate prints or disordered order.

Solution: Refactored Synchronization Mechanism

The best solution refactors the code as follows:

  1. Encapsulates printing logic entirely within the Printer class, using synchronized methods to ensure thread safety.
  2. Introduces a shared state variable isOdd as a conditional flag for inter-thread communication.
  3. Distinguishes between even and odd threads via constructor parameters in TaskEvenOdd, with independent management of their number sequences.
public class PrintEvenOddTester {
    public static void main(String... args) {
        Printer print = new Printer();
        Thread t1 = new Thread(new TaskEvenOdd(print, 10, false));
        Thread t2 = new Thread(new TaskEvenOdd(print, 10, true));
        t1.start();
        t2.start();
    }
}

class TaskEvenOdd implements Runnable {
    private int max;
    private Printer print;
    private boolean isEvenNumber;

    TaskEvenOdd(Printer print, int max, boolean isEvenNumber) {
        this.print = print;
        this.max = max;
        this.isEvenNumber = isEvenNumber;
    }

    @Override
    public void run() {
        int number = isEvenNumber ? 2 : 1;
        while (number <= max) {
            if (isEvenNumber) {
                print.printEven(number);
            } else {
                print.printOdd(number);
            }
            number += 2;
        }
    }
}

class Printer {
    boolean isOdd = false;

    synchronized void printEven(int number) {
        while (!isOdd) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("Even:" + number);
        isOdd = false;
        notifyAll();
    }

    synchronized void printOdd(int number) {
        while (isOdd) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("Odd:" + number);
        isOdd = true;
        notifyAll();
    }
}

In this implementation, the printEven and printOdd methods use a while loop to check the condition isOdd. If the condition is not met, they call wait() to release the lock and enter a waiting state. When the condition is satisfied, they print the number, update the state, and wake up other waiting threads via notifyAll(). This design ensures strict alternating execution: after the odd thread prints, it sets isOdd = true, allowing the even thread to execute only when isOdd == true, and vice versa.

Core Knowledge Points: Thread Synchronization and Communication

This case involves the following core concepts of multithreaded programming:

It is important to note that a while loop is used for condition checking instead of an if statement to prevent spurious wakeups, ensuring that execution continues only when the condition is truly met.

Comparison with Other Implementation Approaches

Beyond the best solution, other answers provide different implementation methods:

In comparison, the best solution excels in clarity, scalability, and correctness, making it more suitable as a teaching example and production reference.

Practical Recommendations and Extensions

In practical development, when handling similar multithreading synchronization problems, it is recommended to:

  1. Clearly define shared resources and synchronization boundaries, using locks or synchronized blocks to protect critical sections.
  2. Prefer advanced concurrency tools such as classes in the java.util.concurrent package, which provide more powerful and less error-prone abstractions.
  3. Validate the correctness of multithreaded behavior through testing, especially in high-concurrency scenarios.

This case can be extended to more general problems, such as alternating printing with N threads, by introducing more complex state machines or queue mechanisms. Understanding the foundational principles of this case is a crucial step in mastering advanced multithreaded programming.

Copyright Notice: All rights in this article are reserved by the operators of DevGex. Reasonable sharing and citation are welcome; any reproduction, excerpting, or re-publication without prior permission is prohibited.