notifyAll()无法唤醒全部线程问题:三线程场景下的异常排查
问题分析与解决方案
核心原因
你的代码里第二个线程无法被唤醒的关键问题在于:
methodB持有锁期间调用notifyAll,被唤醒的线程无法立即执行:methodB是synchronized方法,线程thread3Notify进入后会一直攥着main对象的锁,直到方法执行完才释放。第一次notifyAll会把thread0和thread1从等待队列里唤醒,但这俩线程得拿到锁才能继续执行wait()之后的代码,而锁还在thread3Notify手里,所以它们会陷入锁阻塞状态,而非等待状态。- 第二次
notifyAll完全无效:当thread3Notify调用第二次notifyAll时,thread0和thread1已经不在等待队列里了,这次调用对它们没有任何作用。 - 锁竞争的结果偏差:等
methodB执行完释放锁,thread0和thread1会抢锁。如果thread0先拿到锁并跑完methodA,thread1拿到锁后本应继续执行,但你的输出里没出现,大概率是代码里sleep没正确处理异常(比如Thread.sleep()需要捕获InterruptedException),或者线程执行时机导致的表象,但本质问题还是锁持有时间太长,让唤醒逻辑失效了。
修正方案
要实现逐个唤醒线程的需求,得让每次唤醒后,被唤醒的线程有机会拿到锁执行,同时避免无意义的notifyAll调用。推荐两种可行方案:
方案1:用notify()逐个唤醒 + 释放锁等待
修改methodB,每次调用notify()后释放锁,等一段时间再重新拿锁唤醒下一个线程:
public void methodB() { synchronized (this) { System.out.println("唤醒第一个线程"); this.notify(); } try { Thread.sleep(1000); // 留时间给第一个线程执行 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } synchronized (this) { System.out.println("唤醒第二个线程"); this.notify(); } }
同时给methodA加上循环等待(这是Java多线程的最佳实践,避免虚假唤醒):
public synchronized void methodA() { System.out.println("I enter methodA" + Thread.currentThread()); try { this.wait(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } System.out.println("I am awake " + Thread.currentThread()); }
方案2:用notifyAll()配合条件变量
如果必须用notifyAll(),可以通过条件变量标记需要唤醒的线程,让线程在wait()前检查条件,确保只有满足条件的线程才继续执行:
private int wakeCount = 0; public synchronized void methodA() { System.out.println("I enter methodA" + Thread.currentThread()); try { // 循环等待,避免虚假唤醒,直到满足计数条件 while (wakeCount == 0) { this.wait(); } wakeCount--; } catch (InterruptedException e) { Thread.currentThread().interrupt(); } System.out.println("I am awake " + Thread.currentThread()); } public synchronized void methodB() { try { wakeCount++; this.notifyAll(); Thread.sleep(1000); wakeCount++; this.notifyAll(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }
关键注意事项
- 永远在
while循环里调用wait(),防止虚假唤醒(线程可能没被notify/notifyAll就醒来)。 - 调用
wait()、notify()、notifyAll()时,必须持有对应对象的锁。 Thread.sleep()不会释放锁,只有wait()会主动释放锁。
内容的提问来源于stack exchange,提问作者Klaus Köhler
相关产品推荐
相关产品推荐

