需用 notifyall() 配合 volatile 状态变量(如 turn)实现线程顺序执行,因 notify() 随机唤醒易导致死锁或乱序;reentrantlock+condition 可精确唤醒指定线程;semaphore 以令牌传递方式最直观但需防异常泄漏。

用 wait() 和 notifyAll() 控制线程执行顺序
Java 中没有原生的“按序唤醒某一个线程”机制,notify() 是随机唤醒,所以必须用 notifyAll() 配合状态判断(如 volatile int state)来避免丢失信号。三个线程分别打印 A、B、C,目标是输出 “ABCABC…”——关键不是谁先启动,而是谁该轮到自己执行时才真正打印。
常见错误是只靠 wait()/notify() 而不检查条件,导致虚假唤醒后直接执行,打乱顺序;或者用 notify() 但被唤醒的不是目标线程,造成死锁。
- 定义一个共享
volatile int turn = 0:0 表示轮到 A,1 表示 B,2 表示 C - 每个线程循环中用
synchronized块包裹,先while (turn != expected)判断,再wait() - 打印完立即更新
turn并调用notifyAll() - 注意:
wait()必须在synchronized块内调用,否则抛IllegalMonitorStateException
用 ReentrantLock + Condition 精确唤醒指定线程
比 wait/notify 更可控:可以为 A、B、C 各建一个 Condition,A 打印完就 signal() B 的 condition,B 再 signal C,C 再 signal A,形成闭环。避免了 notifyAll() 唤醒所有线程再竞争的开销,也彻底规避虚假唤醒问题。
但要注意:Condition 必须由同一个 ReentrantLock 实例创建,且 await() / signal() 必须在 lock() 之后、unlock() 之前调用。
立即学习“Java免费学习笔记(深入)”;
- 声明
ReentrantLock lock = new ReentrantLock(); - 定义三个
Condition:condA = lock.newCondition();、condB、condC - A 线程逻辑:获取锁 →
while (state != 0) condA.await();→ 打印 A →state = 1;→condB.signal();→ 释放锁 - 初始时需手动
condA.signal()启动第一个线程,否则全部 await 停住
用 Semaphore 实现三信号量接力
语义最清晰:用三个 Semaphore 分别代表“A 可执行”“B 可执行”“C 可执行”,初始仅 A 的 permit 为 1,其余为 0。每个线程执行前 acquire() 自己的信号量,打印后 release() 下一个的信号量。完全无锁,也不依赖共享变量判断。
缺点是信号量本身有轻微开销,且如果某个线程异常退出,未 release() 下一个 semaphore,整个流程就会卡死——生产环境需加 try-finally 保证释放。
- 初始化:
Semaphore semA = new Semaphore(1);,semB = new Semaphore(0);,semC = new Semaphore(0); - A 线程:
semA.acquire(); System.out.print("A"); semB.release(); - B 线程:
semB.acquire(); System.out.print("B"); semC.release(); - C 线程:
semC.acquire(); System.out.print("C"); semA.release();
为什么不用 Thread.join() 或 CountDownLatch?
join() 是单次等待,无法构成循环协作;CountDownLatch 也是“一次减到底就永远打开”,不能重置。它们适合“等全部做完再继续”,而不是“轮流做、反复做”。如果硬要用,得配合外部循环和多个 latch,代码反而臃肿且易出错。
真正需要的是可重复使用的同步点,所以 wait/notify、Condition、Semaphore 是正解。其中 Semaphore 最贴近“令牌传递”的直觉,但初学者容易忽略异常路径下的资源泄漏;Condition 最精确,但 API 稍重;wait/notify 最基础,但必须写对 while 循环和 notifyAll 才安全。
实际调试时,如果输出卡在某个字母不动,八成是唤醒没发出去,或状态变量没及时更新——建议加日志打在 print 前后,确认线程是否真的进入/离开临界区。











