Java中notify与notifyAll的核心区别与执行逻辑解析
本文深入解析Java中notify和notifyAll的异同。重点阐述它们如何影响线程状态转换及后续的锁竞争行为,通过代码示例说明为何调用notifyAll后并非所有被通知线程都能立即执行。
结果或目标:明确唤醒策略的实际效果
在并发编程中,使用Object.wait()、notify()和notifyAll()是为了管理线程间的协作。很多开发者存在一个误区,认为notifyAll会像“广播”一样让所有等待线程同时恢复运行。实际上,这两个方法的核心区别在于唤醒的数量,但后续的执行受到对象监视器(锁)的严格限制。
准备条件:同步环境与方法要求
在使用这些方法前,必须满足以下约束:
- 所有者限制:只有获得对象监视器(即持有锁)的线程才能调用
wait、notify或notifyAll。这通常通过synchronized块或同步方法实现。 - 状态转移:
wait()会导致当前线程放弃锁并进入等待集。notify/notifyAll则是从等待集中移除线程,使其重新进入锁竞争队列。
真实操作顺序:锁竞争机制深度解析
第1步:设置等待环境
定义一个资源对象作为监视器,并创建多个线程进入等待状态。每个线程在获取锁后调用wait()挂起自身。
private static final Object obj = new Object();
static class R implements Runnable {
int i;
R(int i) { this.i = i; }
public void run() {
try {
synchronized(obj) {
System.out.println("线程-> " + i + " 等待中");
obj.wait(); // 放弃锁并等待
System.out.println("线程-> " + i + " 在运行了");
Thread.sleep(30000); // 模拟长时间任务,持有锁
}
} catch(Exception e) {
e.printStackTrace();
}
}
}
第2步:触发唤醒操作
主线程启动上述多个工作线程后,短暂休眠以便所有线程进入等待状态,随后在主线程持有的锁内调用notifyAll()。
Thread[] rs = new Thread[10];
for(int i = 0; i < 10; i++) {
rs[i] = new Thread(new R(i));
}
for(Thread r : rs) {
r.start();
}
Thread.sleep(5000);
// 主线程获取锁并通知所有等待者
synchronized(obj) {
obj.notifyAll();
}
第3步:观察锁竞争与执行结果
当notifyAll被调用时,所有处于等待集的线程都被移出,并重新尝试获取obj上的锁。由于synchronized的单线程持有特性,同一时间只能有一个线程成功获得锁。
- 输出现象:控制台会打印出所有线程“等待中”,但在
notifyAll之后,通常只会有一个线程打印“在运行了”。 - 原因分析:第一个获取到锁的线程开始执行其后的代码(包括
sleep(30000))。在此期间,其他被唤醒的线程处于“锁竞争失败”状态,它们在原地排队等待,直到前一个线程释放锁。
完成判断与核心结论
要判断线程是否真正“继续运行”,需满足两个必要条件:
- 被
notify或notifyAll唤醒(状态从WAITING变为BLOCKED)。 - 成功通过竞争获取到对象的监视器锁。
因此,notify和notifyAll在极端情况下可能产生相同的单线程执行效果,但适用场景不同:
notify:适用于只需唤醒单一竞争者即可推进任务的场景(如单一生产者-单一消费者),效率高但易丢失唤醒信号。notifyAll:适用于需要所有或多数等待者检查条件并继续处理的场景,虽然引入了额外的锁竞争开销,但能确保不遗漏任何需要响应的线程。
简要总结
理解notify与notifyAll的关键在于区分“唤醒动作”与“锁获取动作”。唤醒只是解除了线程的睡眠状态,让其进入争抢资源的队列;真正的执行权取决于谁能在随后的竞争中获得锁。合理选择两者能有效提升系统的响应性与吞吐量。
以上就是Java notify和NotifyAll的区别的详细内容,更多关于Java线程调度的资料请关注本站其它相关文章!
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









