wait 和 notify 存在那些问题,如何正确使用
wait() 和 notify() 是 Java 中用于线程间协作的两个方法,它们是 Object 类的一部分。它们通常与 synchronized 一起使用,以实现线程间的通信,但是也存在一些问题,我们应该合理的使用 wait() 和 notify()。
wait 和 notify 存在的问题
过早唤醒
当线程被 notify() 唤醒时,如果条件并未满足,线程可能会继续执行,这被称为过早唤醒。过早唤醒使得无须被唤醒的等待线程也被唤醒了,导致资源浪费。
线程唤醒的不确定性
notify() 方法随机唤醒一个等待线程,这可能导致线程饥饿,特别是如果某些线程经常被唤醒而其他线程很少或从未被唤醒。
信号丢失:
导致信号丢失的情况有两种,一种是在循环体外判断保护条件,另一种是 notify() 方法使用不当。
-
循环体外判断条件: 如果等待线程在执行
wait()方法前没有判断保护条件是否成立,那么有可能导致通知线程在等待线程进入临界区前就更新了共享变量,使得保护条件成立,并进行了通知,但是等待线程并没有暂停,所以也没有被唤醒。这种现象相当于等待线程错过了一个发送给它的“信号”,所以叫信号丢失。只要对保护条件的判断和wait()方法的调用放在循环语句中,就可以避免这种情况导致的信号丢失。 -
notify()使用不当: 信号丢失的另一个表现是在应该调用notifyAll()的情况下调用了 notify (),在这种情况下,避免信号丢失的办法是使用notifyAll()进行通知
欺骗性唤醒
等待线程可能在没有其他线程执行 notify()/notifyAll() 的情况下被唤醒,这种现象叫欺骗性唤醒。
死锁风险:
如果 wait() 和 notify() 的使用不当,可能会导致死锁。例如,如果一个线程在等待另一个线程的通知,而另一个线程在等待第一个线程的通知,且两者都没有释放锁,就会导致死锁
使用 wait () 和 notify ()
为了避免上述问题,我们应该合理的使用 wait() 和 notify()。
-
使用循环检查条件:在调用
wait()之前,应该先检查条件是否满足。如果条件不满足,才调用wait()。这样可以避免过早唤醒问题。 -
确保同步:
wait()和notify()必须在同步块中调用,以确保线程安全。只有持有对象锁的线程才能调用wait()和notify()。 -
使用
notifyAll():当有多个线程在等待时,使用notifyAll()可以唤醒所有等待的线程,而不是只唤醒一个 -
使用 final 修饰 lock 对象: 之所以要使用 final 修饰 lock 对象,是因为如果没有用 final 修饰,那么这个对象的值可能被修改,导致等待线程和通知线程同步在不同的内部锁上
public class ProducerConsumer {
private final Object lock = new Object();
private boolean isProduced = false;
public void produce() {
synchronized (lock) {
while (isProduced) {
try {
lock.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
isProduced = true;
lock.notifyAll();
}
}
public void consume() {
synchronized (lock) {
while (!isProduced) {
try {
lock.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
isProduced = false;
lock.notifyAll();
}
}
}
更多大厂面试题,欢迎前往微信搜索小程序 「猿面试」 查看。微信小程序 (猿面试) 包含了 Java、Android、鸿蒙和ArkTS、设计模式、算法和数据结构 相关内容,
Hi 大家好,我是 DHL,大厂程序员,公众号:ByteCode ,在美团、快手、小米工作过。搞过逆向,做过性能优化,研究过系统,擅长鸿蒙、Android、Kotlin、性能优化、职场分享。
更多面试题:
- 从字节码看 finally 的本质,你能说出这些代码运行结果吗?
- 用final声明的局部变量,能提升性能吗
- 线程池解决什么问题,为什么不推荐使用Executors创建线程池
- 线程同步有那些方式
- 为什么成员变量不需要手动初始化,而局部变量需要手动初始化
- 操作符+号和StringBuilder性能差异
- 一道奇葩的面试题,线程调用2次start方法会怎样
- 鸿蒙,ArkTs 一段诡异的代码
- 揭秘sleep和wait,notify和notifyAll解决什么问题
开源新项目
-
云同步编译工具(SyncKit),本地写代码,远程编译,欢迎前去查看 SyncKit
-
KtKit 小巧而实用,用 Kotlin 语言编写的工具库,欢迎前去查看 KtKit
-
最全、最新的 AndroidX Jetpack 相关组件的实战项目以及相关组件原理分析文章,正在逐渐增加 Jetpack 新成员,仓库持续更新,欢迎前去查看 AndroidX-Jetpack-Practice
-
LeetCode / 剑指 offer,包含多种解题思路、时间复杂度、空间复杂度分析,在线阅读