wait的虚假唤醒
一,什么是wait()虚假唤醒
1. 造成wait()虚假唤醒的因素
一,是多线程环境(有多个线程拿到了同一个对象锁,并进行了wait()阻塞等待)。
二,是拥有该对象锁的线程调用了notifyAll()方法。
如果在多线程环境中满足了以上两个条件就有可能造成wait()虚假唤醒。
2. 虚假唤醒的例子
什么是虚假唤醒?接下来我将会用一个非常通俗移动的例子来举例。
【例1】:我有两个帮我干活的员工此时都正在等待我发布任务(相当于wait())并且要做了我发布的任务才能下班,但是我却只发布了一个任务并且我在发布任务后有事先走了,而我也没有通知只让一个人来完成这个任务(如果是指定一个人完成工作就调用notify()),我是通知所有人说有任务可以做了,那么所有的人都来竞争做这个任务然后下班,但是一个任务只能由第一个获取任务的人来完成并且完成后就下班,后到的那个人就做不到任务,此时没有做到任务的人也没有得到其他任何通知心里很懵逼(心想老板不是在虎人?)也气愤的下班了。
【例1】两个员工只有一个员工做了任务,但是两个员工都下班了。这就是一个虚假唤醒的例子。
那么如何避免上述的虚假唤醒呢?【例2】:我有两个帮我干活的员工此时都正在等待我发布任务(相当于wait())并且要做了我发布的任务才能下班,但是我却只发布了一个任务并且我在发布任务后有事先走了,而我也没有通知只让一个人来完成这个任务(如果是指定一个人完成工作就调用notify()),我是通知所有人说有任务可以做了,那么所有的人都来竞争做这个任务然后下班,但是一个任务只能由第一个获取任务的人来完成并且完成后就下班,后到的那个人就做不到任务,但是我走之前给他们发了通知如果来做任务时没有看到任务,那么就请回去重新等着我发布新任务后再通知你们来做任务。此时没有做到任务但是收到了我通知的人心里很气愤(心里想老板TMD),但是没办法没做任务就是不能下班,所有就又继续等我发布任务通知他做任务。
这样我通过一个通知就能够让没有做到任务的人知道他还没有到下班的时候,这样就解决了虚假唤醒的问题。
当然作为java程序猿的我们肯定得通过java代码来实现解决办法。请继续往下看!
二,java在多线程情景下是如何产生wait()虚假唤醒情况的
这是出现了虚假唤醒问题的代码
public class Demo5 {
public static void main(String[] args) {
DataTow dataTow = new DataTow();
// 苦逼员工一号
new Thread(() -> {
dataTow.work();
},"苦逼员工一号").start();
// 苦逼员工二号
new Thread(() -> {
dataTow.work();
},"苦逼员工二号").start();
// 心地善良的老板
new Thread(() -> {
dataTow.issue();
},"可爱的老板").start();
}
}
class DataTow {
// 任务数量
public volatile int number = 0;
// 员工做任务动作
public synchronized void work() {
try {
if(number == 0) {
System.out.println(Thread.currentThread().getName() + "正在等待老板通知");
// 没有任务就阻塞等待
wait();
}
// 被唤醒后做的任务
number--;
System.out.println(Thread.currentThread().getName() + "完成了工作并下班");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
// 老板发布任务动作
public synchronized void issue() {
try {
// 等待3秒让员工都提前进入等待环节
System.out.println(Thread.currentThread().getName() + "正在让苦逼员工愉快的等待中...");
Thread.sleep(3000);
// 老板发布任务
number++;
System.out.println(Thread.currentThread().getName() + "发布了任务并且喝了一口茶后通知了员工。");
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// 通知所有员工都来做任务,并且有事先走了
this.notifyAll();
}
}
=========================
运行结果:
苦逼员工一号正在等待老板通知
苦逼员工二号正在等待老板通知
可爱的老板正在让苦逼员工愉快的等待中...
可爱的老板发布了任务并且喝了一口茶后通知了员工。
苦逼员工二号完成了工作并下班
苦逼员工一号完成了工作并下班
Process finished with exit code 0
三,如何解决wait()虚假唤醒问题
这是没有出现虚假唤醒的代码
public class Demo6 {
public static void main(String[] args) {
DataThree dataThree = new DataThree();
// 苦逼员工一号
new Thread(() -> {
dataThree.work();
},"苦逼员工一号").start();
// 苦逼员工二号
new Thread(() -> {
dataThree.work();
},"苦逼员工二号").start();
// 心地善良的老板
new Thread(() -> {
dataThree.issue();
},"可爱的老板").start();
}
}
class DataThree {
// 任务数量
public volatile int number = 0;
// 员工做任务动作
public synchronized void work() {
try {
// 将 if 修改为 while 来解决虚假唤醒问题
while (number == 0) {
System.out.println(Thread.currentThread().getName() + "正在等待老板通知");
// 没有任务就阻塞等待
wait();
}
// 被唤醒后做的任务
number--;
System.out.println(Thread.currentThread().getName() + "完成了工作并下班");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
// 老板发布任务动作
public synchronized void issue() {
try {
// 等待3秒让员工都提前进入等待环节
System.out.println(Thread.currentThread().getName() + "正在让苦逼员工愉快的等待中...");
Thread.sleep(3000);
// 老板发布任务
number++;
System.out.println(Thread.currentThread().getName() + "发布了任务并且喝了一口茶后通知了员工。");
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// 通知所有员工都来做任务,并且有事先走了
this.notifyAll();
}
}
=========================
运行结果:
苦逼员工一号正在等待老板通知
苦逼员工二号正在等待老板通知
可爱的老板正在让苦逼员工愉快的等待中...
可爱的老板发布了任务并且喝了一口茶后通知了员工。
苦逼员工二号完成了工作并下班
苦逼员工一号正在等待老板通知
|
=========================
此时程序就并未结束,员工一号正在等待老板的通知。
四,解决wait()虚假唤醒的原理
首先看看出现这个问题的代码部分
- wait() 等待有一个特性,就是哪里阻塞等待,在被唤醒时就会在哪里爬起来,由于判断 number 在之前就已经执行完成了,在被唤醒时就不会再进行判断 number 了,此时就发生了一个问题,“缺失新版本判定” ,在第一个员工(首先获取锁资源的线程)执行了任务后,后面的员工(后序获取锁资源的线程)不能及时的感知到任务已经被人做了,所有后来的员工也执行了后序代码块,而这就是造成wait()虚假唤醒的原因。
- 而使用while循环后,第一个员工就会循环一个判断 number 因为老板发布了一个任务,此时的 number = 1,所以第一个员工(首先获取锁资源的线程)就会跳出循环执行任务,当第二个员工(后面获取锁资源的线程)进行判断时 number 又为 0 了,所有就会继续执行到 wait() 方法。这样就解决了虚假唤醒问题了。
喜欢的铁汁动动可爱的小手点点赞吧