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();
}
}
}
Binder事务缓冲区的大小是1MB 吗 为什么使用Bundle而不使用 HashMap ThreadLocal无法在多个线程之间进行上下文信息传递 ThreadLocal 可能会造成数据污染 点击返回键,进程会退出吗 主线程结束了,子线程是否可以正常运行 从字节码看 finally 的本质,你能说出这些代码运行结果吗? 用final声明的局部变量,能提升性能吗 线程池解决什么问题,为什么不推荐使用Executors创建线程池
👇🏻 真诚推荐你关注我👇🏻