
本文详解 java 中多线程协作打印 1 到 n 自然数时出现无限等待的根本原因——同步锁对象不一致,并提供完整可运行的修复方案,涵盖正确加锁、条件等待、唤醒机制及关键注意事项。
本文详解 java 中多线程协作打印 1 到 n 自然数时出现无限等待的根本原因——同步锁对象不一致,并提供完整可运行的修复方案,涵盖正确加锁、条件等待、唤醒机制及关键注意事项。
在使用两个线程(如 odd 和 even)交替打印自然数时,一个常见却隐蔽的错误是:每个线程在各自的 Runnable 实例上执行 synchronized(this),导致它们操作的是互不相关的锁对象。这意味着 odd 线程调用 wait() 后,永远无法被 even 线程的 notify() 唤醒(反之亦然),因为二者根本不在同一个监视器(monitor)上通信——结果就是至少一个线程永久阻塞,程序卡死在“1”。
✅ 正确做法:共享同一把锁
必须让两个线程共用同一个锁对象(如 static final Object lock = new Object();),所有 synchronized、wait() 和 notify() 都作用于该对象:
import java.util.Scanner;
public class OddEven {
static final Object lock = new Object(); // ✅ 全局唯一锁对象
static int totalNos;
static int counter = 1;
static Runnable odd = () -> {
synchronized (lock) {
while (counter {
synchronized (lock) {
while (counter <h3>? 关键要点说明</h3>
- synchronized(lock) 与 lock.wait()/notify() 必须匹配:wait() 和 notify() 只能在当前已获得的同步锁上调用,否则抛出 IllegalMonitorStateException。
- 使用 while 而非 if 判断条件:防止虚假唤醒(spurious wakeup),确保条件真正满足才继续执行。
- 边界条件注意:原代码中 counter
- 中断处理要规范:捕获 InterruptedException 后建议恢复中断状态(Thread.currentThread().interrupt()),而非仅打印堆栈。
- 主线程需 join():避免 main 提前退出导致输出被截断或控制台混乱。
? 补充建议(进阶优化)
- 若追求更高可读性与类型安全,可改用 ReentrantLock + Condition(如 oddCondition.await() / evenCondition.signal()),实现更精准的线程定向唤醒;
- 生产环境中应避免 System.out.print 这类非线程安全 I/O 作为核心逻辑,可先收集结果再统一输出;
- 多线程调试时,善用 jstack
查看线程堆栈,快速定位 WAITING 或 BLOCKED 状态线程及其锁持有关系。
通过统一锁对象、严格遵循 wait/notify 使用契约,即可彻底解决“线程无限等待”问题,实现两个线程严谨交替、有序输出 1 到 N 的自然数。










