问:Java 多线程中 wait() 方法为什么要放在同步块中?
答:为了避免「lost wake up 问题」,即「无法唤醒问题」。
什么是「lost wake up 问题」
我对「lost wake up 问题」的通俗理解:线程 A 调用 wait()
方法进入阻塞状态,接下来没有其他线程去唤醒线程 A,或者其他线程唤醒时机不对(早于线程 A 的 wait()
),导致线程 A 永远阻塞下去。
有中文资料对这个问题作出过解释:Java中wait()方法为什么要放在同步块中?(lost wake-up 问题),文中举了生产者和消费者的例子,但我觉得此文的结论并未触达核心,需要对文中的例子和结论多做一下补充,理解起来会方便一点。
现在有一个生产者线程和消费者线程:
先定义一个 obj 对象,并将其 count 属性的初始值设置为 0:
Object obj = new Object();
obj.count = 0;
生产者伪代码:
obj.count++;
obj.notify();
消费者伪代码:
while(obj.count<=0)
obj.wait();
obj.count--;
两个线程启动,消费者检查 obj.count
的值,发现 obj.count <= 0
条件成立,但这时由于 CPU 的调度,发生上下文切换,生产者开始工作,执行了 count+1
和 obj.notify()
,也就是发出通知,准备唤醒一个阻塞的线程。然后 CPU 调度到消费者,此时消费者开始执行 obj.wait()
,线程进入阻塞。但生产者已经早在消费者阻塞前执行了唤醒动作,也就导致消费者永远无法醒来了。
随便加个锁能解决「lost wake up 问题」吗
不能,举个例子。
定义一把锁:
Lock lock1 = new Lock();
生产者伪代码:
lock1.lock();
obj.count++;
obj.notify();
lock1.unlock();
消费者伪代码:
lock1.lock();
while(count<=0)
obj.wait();
obj.count--;
lock1.unlock();
两个线程启动,obj.count
初始值为 0。假设消费者先竞争到锁,while 中的 obj.count<=0
条件满足,执行 obj.wait()
使线程进入阻塞状态,lock1 锁没有被释放,所以生产者拿不到锁,也就无法 obj.notify()
通知消费者醒来,消费者将永远阻塞下去。
Java 中什么锁才能解决「lost wake up 问题」
只有上述例子中的 obj 对象锁才能避免这个问题,也就是将 obj.wait()
和 obj.notify()
放进 obj 对象锁的同步块中。如果锁的不是例子中的 obj 对象,Java 就会抛出 IllegalMonitorStateException
异常。
生产者伪代码:
synchronized (obj) {
obj.count++;
obj.notify();
}
消费者伪代码:
synchronized (obj) {
while(count<=0)
obj.wait();
obj.count--;
}
Java 中对 wait() 方法的注释中提到:线程在调用 obj.wait()
前必须要拿到当前 obj 对象的监视器 monitor 对象,即 obj 的锁。只有这样,当执行到 obj.wait()
时,该线程才可以暂时让出 obj 的同步锁并停止对锁的竞争,让其他正在等待此锁的线程可以得到同步锁并运行。
在上述例子中,消费者执行到 obj.wait()
时,让出了 obj 锁,停止了对锁的竞争,进入阻塞状态,紧接着生产者竞争到 obj 锁,执行了 obj.notify()
方法,唤醒了消费者,使消费者线程从阻塞状态重新回到就绪状态。
这里要注意的是,obj.notify()
并不是让生产者马上释放锁,也不是让消费者马上得到锁,而是通知消费者线程可以重新去参与锁的竞争了。
大袋鼠?
嗬,好久不见啊~
lock1.lock();
while(count<=0)
obj.wait();
obj.count--;
lock1.unlock();
上述方式有问题,正确的写法应该是:
Condition condition = lock.newCondition();
lock.lock();
try{
while(count<=0){
condition.awit();
}
count--;
}finally{
lock.unlock();
}
该种方式下,即使消费者先获取到锁,也是会最终释放掉锁的,而不是文中所说的
只有obj 对象锁才能避免Lost Wake up 问题。
文中写的 lock.lock 并非api的这个lock
文中表达的想法我理解是 sync关键字 里面的wait 与notify 与sync(object) 对应的锁对象都为同一个
比如 sync(obj) obj.wait 会释放sync获取的锁,
java一个高薪的编程程序。
评论区样式不错,
沙发,我就爱看这种