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:
- The
numbervariable in theTaskEvenOddthread task is an instance variable, with each thread having its own copy, leading to incorrect synchronization of counting. - Improper initialization of the
isOddparameter in thePrinterconstructor results in erroneous initial state settings. - Mixing direct printing and synchronized method calls in thread logic causes output chaos.
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:
- Encapsulates printing logic entirely within the
Printerclass, usingsynchronizedmethods to ensure thread safety. - Introduces a shared state variable
isOddas a conditional flag for inter-thread communication. - 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:
- Synchronized Methods: Using the
synchronizedkeyword to modify methods ensures that only one thread can execute the method at a time, preventing data races. - Wait/Notify Mechanism:
wait()causes the current thread to release the lock and wait until another thread callsnotify()ornotifyAll(). This implements conditional synchronization between threads. - Shared State Variable:
isOddserves as a boolean flag to coordinate the execution order of even and odd threads, acting as a medium for thread communication. - Thread-Safe Design: Encapsulating shared data and synchronization logic within the
Printerclass follows the principle of high cohesion, reducing the risk of race conditions.
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:
- Using
AtomicIntegerand Thread Naming: Ensures atomicity of counting through the atomic variableAtomicInteger, combined with thread names to distinguish even and odd logic. This method simplifies synchronization but may lack flexibility due to reliance on thread naming. - Single-Class Implementation: Integrates all logic into one class, using
volatilevariables and explicit lock objects. This implementation is compact but offers poorer readability and maintainability, with potential output beyond the range (e.g., printing up to 11).
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:
- Clearly define shared resources and synchronization boundaries, using locks or synchronized blocks to protect critical sections.
- Prefer advanced concurrency tools such as classes in the
java.util.concurrentpackage, which provide more powerful and less error-prone abstractions. - 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.