wait()方法简介:
/**
* Causes the current thread to wait until another thread invokes the
* {@link java.lang.Object#notify()} method or the
* {@link java.lang.Object#notifyAll()} method for this object.
* In other words, this method behaves exactly as if it simply
* performs the call {@code wait(0)}.
* <p>
* The current thread must own this object's monitor. The thread
* releases ownership of this monitor and waits until another thread
* notifies threads waiting on this object's monitor to wake up
* either through a call to the {@code notify} method or the
* {@code notifyAll} method. The thread then waits until it can
* re-obtain ownership of the monitor and resumes execution.
* <p>
* As in the one argument version, interrupts and spurious wakeups are
* possible, and this method should always be used in a loop:
* <pre>
* synchronized (obj) {
* while (<condition does not hold>)
* obj.wait();
* ... // Perform action appropriate to condition
* }
* </pre>
* This method should only be called by a thread that is the owner
* of this object's monitor. See the {@code notify} method for a
* description of the ways in which a thread can become the owner of
* a monitor.
*
* @throws IllegalMonitorStateException if the current thread is not
* the owner of the object's monitor.
* @throws InterruptedException if any thread interrupted the
* current thread before or while the current thread
* was waiting for a notification. The <i>interrupted
* status</i> of the current thread is cleared when
* this exception is thrown.
* @see java.lang.Object#notify()
* @see java.lang.Object#notifyAll()
*/
public final void wait() throws InterruptedException {
wait(0);
}
wait()和notify()一系列的方法,是属于对象的,不是属于线程的。它们用在线程同步时,synchronized语句块中,wait会让出CPU而notify不会,notify重在于通知使用object的对象“我用完了!”,wait重在通知其它同用一个object的线程“我暂时不用了”并且让出CPU。
为什么要将wait()放在同步代码块中呢?
Lost Wake-Up Problem
首先我们来举个例子,一个消费者线程、一个生产者线程。生产者的任务为count+1,然后唤醒消费者;消费者的任务为count-1,等到count为0时陷入沉睡。
生产者伪代码:
count++;
notify();
消费者伪代码:
while(count <= 0){
wait();
count--;
}
熟悉多线程的朋友应该一眼就看出来了问题,如果生产者和消费者的步骤混杂在一起会发生什么。
首先我们先假设count = 0,这个时候消费者检查count的值,发现count <= 0的条件成立;就在这个时候,发生了上下文切换,生产者进来了,噼噼啪啪一顿操作,把两个步骤都执行完了,也就是发出了通知,准备唤醒一个线程。这个时候消费者刚决定睡觉,还没睡呢,所以这个通知就会被丢掉。紧接着,消费者就睡过去了……
这就是所谓的Lost Wake-Up Problem
如何解决
现在我们应该能发现问题的根源在于,消费者在检查count到调用wait()之间,count就可能被改掉了。 那我们如何解决呢? 让消费者和生产者竞争一把锁,竞争到了的,才能够修改count的值。
于是乎生产者代码:
lock();
count++;
notify();
unlock();
消费者代码:
lock();
while(count <= 0){
wait();
count--;
}
unlock();
现在我们来看看,这样子真的解决了吗?
答案是毫无卵用,依旧会出现lost wake up问题,而且和无锁的表现是一样的。
因为wait()是释放锁然后等待获取锁,当然要先获得锁才行,但在这边连锁都莫得。
终极答案
所以,我们可以总结到,为了避免出现这种 lost wake up 问题,在这种模型之下,总应该将我们的代码放进去的同步块中。
Java强制我们的 wait()/notify() 调用必须要在一个同步块中,就是不想让我们在不经意间出现这种 lost wake up 问题。
不仅仅是这两个方法,包括 java.util.concurrent.locks.Condition 的 await()/signal() 也必须要在同步块中。
正解:
private Object obj = new Object();
private Object anotherObj = new Object();
@Test
public void produce() {
synchronized (obj) {
try {
//同步块要对当前线程负责,而不是anotherObj.notify();
obj.notify();
} catch (Exception e) {
e.printStackTrace();
}
}
}
wait和notify的用法
wait()、notify()和notifyAll()
- wait()、notify() 和 notifyAll()方法是本地方法,并且为 final 方法,无法被重写。
- 调用某个对象的 wait() 方法能让当前线程阻塞,并且当前线程必须拥有此对象的 monitor(即锁,或者叫管程)。
- 调用某个对象的 notify() 方法能够唤醒一个正在等待这个对象的 monitor 的线程,如果有多个线程都在等待这个对象的 monitor,则只能唤醒其中一个线程。
- 调用 notifyAll() 方法能够唤醒所有正在等待这个对象的monitor的线程。
具体应用
/**
* wait() && notify()方法
* 这两个方法是在Object中定义的,用于协调线程同步,比 join 更加灵活
*/
public class NotifyDemo {
public static void main(String[] args) {
//写两个线程 1.图片下载
Object obj=new Object();
Thread download=new Thread(){
public void run() {
System.out.println("开始下载图片");
for (int i = 0; i < 101; i+=10) {
System.out.println("down"+i+"%");
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("图片下载成功");
synchronized (obj) {
obj.notify();//唤起
}
System.out.println("开始下载附件");
for (int i = 0; i < 101; i+=10) {
System.out.println("附件下载"+i+"%");
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("附件下载成功");
}
};
//2.图片展示
Thread show=new Thread(){
public void run(){
synchronized (obj) {
try {
obj.wait();//阻塞当前
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("show:开始展示图片");
System.out.println("图片展示完毕");
}
}
};
download.start();
show.start();
}
}
作者:ZARD007 原文链接:juejin.cn/post/684490…
发布于 2020-03-13