多线程:什么是虚假唤醒?为什么会产生虚假唤醒?

编程入门 行业动态 更新时间:2024-10-25 10:32:47

多线程:什么是<a href=https://www.elefans.com/category/jswz/34/1763621.html style=虚假唤醒?为什么会产生虚假唤醒?"/>

多线程:什么是虚假唤醒?为什么会产生虚假唤醒?

最近B站学习狂神的JUC并发编程时,听到了虚假唤醒这个词,虽然狂神进行了代码的演示,但我还是不太理解为什么使用if判断包装wait方法会出现虚假唤醒,查找了网上很多大佬的博客终于理解了,这里分享一下虚假唤醒产生的原因。

什么是虚假唤醒?
当一定的条件触发时会唤醒很多在阻塞态的线程,但只有部分的线程唤醒是有用的,其余线程的唤醒是多余的。
比如说卖货,如果本来没有货物,突然进了一件货物,这时所有的顾客都被通知了,但是只能一个人买,所以其他人都是无用的通知。

虚假唤醒演示

public class test {public static void main(String[] args) {Product product = new Product();new Thread(() -> {for (int i = 0; i < 10; i++) {try {product.push();} catch (InterruptedException e) {e.printStackTrace();}}}, "生产者A").start();new Thread(() -> {for (int i = 0; i < 10; i++) {try {product.pop();} catch (InterruptedException e) {e.printStackTrace();}}}, "消费者A").start();new Thread(() -> {for (int i = 0; i < 10; i++) {try {product.push();} catch (InterruptedException e) {e.printStackTrace();}}}, "生产者B").start();new Thread(() -> {for (int i = 0; i < 10; i++) {try {product.pop();} catch (InterruptedException e) {e.printStackTrace();}}}, "消费者B").start();}
}``````java
class Product {private int product = 0;public synchronized void push() throws InterruptedException {// System.out.println(Thread.currentThread().getName() + "进入push方法");if (product > 0) {this.wait();}product++;System.out.println(Thread.currentThread().getName() + "添加产品,剩余" + product + "件产品");this.notifyAll();}public synchronized void pop() throws InterruptedException {// System.out.println(Thread.currentThread().getName() + "进入pop方法");if (product == 0) {this.wait();}product--;System.out.println(Thread.currentThread().getName() + "使用产品,剩余" + product + "件产品");this.notifyAll();}
}

程序中定义了两个生产者和两个消费者,产品缓冲区的大小为1,一旦生产者生产了产品,消费者就要去消费而生产者不得再生产。
理论上应该出现的结果:

生产者A添加产品,剩余1件产品
消费者A使用产品,剩余0件产品
生产者A添加产品,剩余1件产品
消费者A使用产品,剩余0件产品
生产者B添加产品,剩余1件产品
消费者A使用产品,剩余0件产品
生产者A添加产品,剩余1件产品

程序实际运行结果为:

生产者A添加产品,剩余1件产品
消费者A使用产品,剩余0件产品
生产者B添加产品,剩余1件产品
生产者A添加产品,剩余2件产品
生产者B添加产品,剩余3件产品
消费者A使用产品,剩余2件产品
消费者A使用产品,剩余1件产品

可以看到程序并没有实现同步的需求。实际上出现的结果可能远不止如此,那为什么会出现这种情况呢?

为了让程序执行步骤更好理解,我在push和pop方法前加入输出语句:

public synchronized void push() throws InterruptedException {System.out.println(Thread.currentThread().getName() + "进入push方法");...
}public synchronized void pop() throws InterruptedException {System.out.println(Thread.currentThread().getName() + "进入pop方法");...
}

执行结果如下:

生产者A进入push方法
生产者A添加产品,剩余1件产品
生产者A进入push方法
消费者A进入pop方法
消费者A使用产品,剩余0件产品
消费者A进入pop方法
生产者A添加产品,剩余1件产品
生产者A进入push方法
生产者B进入push方法
消费者A使用产品,剩余0件产品
消费者A进入pop方法
生产者B添加产品,剩余1件产品
生产者B进入push方法
生产者A添加产品,剩余2件产品
生产者A进入push方法
生产者B添加产品,剩余3件产品

``
步骤分析:

生产者A先进入push方法,此时没有产品,条件判断不成立,生产产品,唤醒其他线程
if (product > 0){this.wait();
}
生产者A进入push方法
生产者A添加产品,剩余1件产品

生产者A继续进入push方法,但是此时已有一个产品,条件满足,进入阻塞队列并释放锁
生产者A进入push方法

消费者A进入pop方法,此时已有产品,条件不满足,使用一个产品并唤醒其他线程。
if (product == 0) {
    this.wait();
}

消费者A进入pop方法
消费者A使用产品,剩余0件产品

消费者A的CPU时间片未结束,继续进入pop方法,但此时已没有产品了,进入阻塞队列并释放锁
消费者A进入pop方法
1
由于步骤3已经唤醒了生产者A线程(注意生产者A停留在if代码块中),此时生产者A直接跳出 if 代码块并添加产品并唤醒其他线程
生产者A添加产品,剩余1件产品
1
生产者A时间片未结束,继续进入push方法,此时有产品,进入阻塞队列
生产者A进入push方法
1
生产者B进入push方法,此时有产品,进入阻塞队列
生产者B进入push方法
1
在步骤5中唤醒了阻塞队列中的消费者A线程,此时消费者A跳出 if 代码块消费产品并唤醒了生产者A线程、生产者B线程,由于时间片未结束,消费者A继续进入pop方法,但此时已经没有产品了,进入阻塞队列
消费者A使用产品,剩余0件产品
消费者A进入pop方法
1
2
经过这么久,终于要到发生同步错误的地方了!!!注意步骤8中消费者A唤醒了位于阻塞队列中的生产者A线程和生产者B线程,而这两个线程此时停留在if代码块中。
首先 CPU时间片给到了生产者B,生产者B生产了一个产品,但时间片未结束,继续进入push方法,此时已有产品,因此生产者B停留在this.wait()处
if (product > 0) {
    this.wait();
}
生产者B添加产品,剩余1件产品
生产者B进入push方法

此时CPU时间片给到了生产者A,生产者A跳出if判断条件,添加一个产品(此时产品变为两个)并唤醒其他线程(生产者B线程又被唤醒了),同样CPU时间片未结束会产生和步骤9生产者线程B同样的操作
生产者A添加产品,剩余2件产品
生产者A进入push方法

在步骤10中生产者B线程又被唤醒,此时CPU时间片又给到生产者B,生产者跳出 if 代码块并生产一个产品(此时产品变为3个)…
生产者B添加产品,剩余3件产品

如此一来,两个生产者就有可能一直往复生产下去,产品数量可能变得很大。同时,若两个消费者一直交替消费产品,那产品数量可能就会出现负数的情况。如下面运行结果:

消费者B进入pop方法
消费者B使用产品,剩余0件产品
消费者B进入pop方法
消费者A使用产品,剩余-1件产品
消费者A进入pop方法
消费者A使用产品,剩余-2件产品
消费者B使用产品,剩余-3件产品
生产者B添加产品,剩余-2件产品
生产者B进入push方法
生产者B添加产品,剩余-1件产品
生产者A添加产品,剩余0件产品

为什么会产生虚假唤醒?
从上面的例子可以看出,同步失败的主要原因有以下几个点:

生产者唤醒了所有处于阻塞队列中的线程,我们希望的是生产者A唤醒的应该是两个消费者,而不是唤醒了生产者B
我们都知道,wait方法的作用是将线程停止执行并送入到阻塞队列中,但是wait方法还有一个操作就是释放锁。因此当生产者A执行wait方法时,该线程就会把它持有的对象锁释放,这样生产者B就可以拿到锁进入synchronized修饰的push方法中,即使它被卡在if判断,但被唤醒后它就会又添加一个产品了。
如何解决虚假唤醒?
从上面分析可以知道导致虚假唤醒的原因主要就是一个线程直接在if代码块中被唤醒了,这时它已经跳过了if判断。我们只需要将if判断改为while,这样线程就会被重复判断而不再会跳出判断代码块,从而不会产生虚假唤醒这种情况了。

改动后的代码:

public synchronized void push() throws InterruptedException {System.out.println(Thread.currentThread().getName() + "进入push方法");while (product > 0) {this.wait();}product++;System.out.println(Thread.currentThread().getName() + "添加产品,剩余" + product + "件产品");this.notifyAll();}public synchronized void pop() throws InterruptedException {System.out.println(Thread.currentThread().getName() + "进入pop方法");while (product == 0) {this.wait();}product--;System.out.println(Thread.currentThread().getName() + "使用产品,剩余" + product + "件产品");this.notifyAll();}

执行结果如下:

生产者A进入push方法
生产者A添加产品,剩余1件产品
生产者A进入push方法
消费者A进入pop方法
消费者A使用产品,剩余0件产品
消费者A进入pop方法
生产者A添加产品,剩余1件产品
生产者A进入push方法
消费者A使用产品,剩余0件产品
消费者A进入pop方法
生产者A添加产品,剩余1件产品
生产者A进入push方法
消费者A使用产品,剩余0件产品

可以看出,无论CPU时间片给到哪个线程都不会再发生虚假唤醒了

参考:

什么是Java虚假唤醒及如何避免虚假唤醒?《多线程学习之十四》
Java中Synchronized的用法(简单介绍)
java并发编程:wait()和sleep的区别
————————————————
版权声明:本文为CSDN博主「橙不甜橘不酸」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:

更多推荐

多线程:什么是虚假唤醒?为什么会产生虚假唤醒?

本文发布于:2023-12-05 22:10:56,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1665461.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:虚假   多线程

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!