无聊小知识.03 wait(),notify()虚假唤醒

简介: 无聊小知识.03 wait(),notify()虚假唤醒

 1、什么是wait和notify

属于Object类的两个方法。

wait(),使当前线程等待,直到另一个线程使用notify()或notifyAll()方法唤醒。

notify(),唤醒正在wait等待的线程。

官方API文档说明:

image.png

2、 示例代码

既然说明了wait()和notify()的功能,那么我们手撸一段代码来试下:

/**
 * @author Shamee loop
 * @date 2023/4/9
 */
public class ObjectWaitNotifyDemo {
    public static void main(String[] args) {
        NumberOper object = new NumberOper();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                object.add();
            }
        }, "thread-add-1").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                object.sub();
            }
        }, "thread-sub-1").start();
    }
}
/**
 * 定义一个数量操作类
 * 其中包含两个方法,
 * 一个是对临界值number++;当判断number!=0,就等待number变成0后在执行。同时唤醒另一个线程
 * 另一个方法是对临界值number--;当判断number==0,就等待number!=0后在执行。同时唤醒另一个线程
 * 同时两个方法分别加了synchronized锁,确保线程安全问题
 */
class NumberOper {
    private int number = 0;
    public synchronized void add() {
        if(number != 0) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        number++;
        System.out.println("线程" + Thread.currentThread().getName() + "执行了add(),number====>" + number);
        this.notifyAll();
    }
    public synchronized void sub() {
        if(number == 0) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        number--;
        System.out.println("线程" + Thread.currentThread().getName() + "执行了sub(),number====>" + number);
        this.notifyAll();
    }
}

image.gif

预期的结果应该是:

线程tread-add-1执行,判断到number==0,就number++;这时候控制台打印number==>1

线程tread-sub-1执行,判断到number==0,则进行等待;等到tread-add-1执行完毕后,唤醒了tread-sub-1执行,这时候tread-sub-1就执行了number--操作;这时候控制台打印number==>0

实际执行结果:

image.png

果然是这样。那么这篇文章就结束了吗?

3、多个线程执行

上面操作示例,只开启了2个线程。但如果开启多个线程呢?我们再试下:

/**
 * @author Shamee loop
 * @date 2023/4/9
 */
public class ObjectWaitNotifyDemo {
    public static void main(String[] args) {
        NumberOper object = new NumberOper();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                object.add();
            }
        }, "thread-add-1").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                object.sub();
            }
        }, "thread-sub-1").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                object.add();
            }
        }, "thread-add-2").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                object.sub();
            }
        }, "thread-sub-2").start();
    }
}
/**
 * 定义一个数量操作类
 * 其中包含两个方法,
 * 一个是对临界值number++;当判断number!=0,就等待number变成0后在执行。同时唤醒另一个线程
 * 另一个方法是对临界值number--;当判断number==0,就等待number!=0后在执行。同时唤醒另一个线程
 * 同时两个方法分别加了synchronized锁,确保线程安全问题
 */
class NumberOper {
    private int number = 0;
    public synchronized void add() {
        if(number != 0) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        number++;
        System.out.println("线程" + Thread.currentThread().getName() + "执行了add(),number====>" + number);
        this.notifyAll();
    }
    public synchronized void sub() {
        if(number == 0) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        number--;
        System.out.println("线程" + Thread.currentThread().getName() + "执行了sub(),number====>" + number);
        this.notifyAll();
    }
}

image.gif

预期的结果应该是:

线程tread-add-1执行,判断到number==0,就number++;这时候控制台打印number==>1

线程tread-sub-1执行,判断到number==0,则进行等待;等到tread-add-1执行完毕后,唤醒了tread-sub-1执行,这时候tread-sub-1就执行了number--操作;这时候控制台打印number==>0

实际执行结果:

image.png

很明显结果不对了,甚至出现了负数等值。这是为啥?

4、虚假唤醒

我们来看下官方API文档怎么说:

image.png

而文档给出来的建议是,临界值的判断使用while。我们再试下:

public synchronized void add() {
        while(number != 0) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        number++;
        System.out.println("线程" + Thread.currentThread().getName() + "执行了add(),number====>" + number);
        this.notifyAll();
    }
    public synchronized void sub() {
        while(number == 0) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        number--;
        System.out.println("线程" + Thread.currentThread().getName() + "执行了sub(),number====>" + number);
        this.notifyAll();
    }

image.gif

执行结果:

image.png

似乎一切正常了。那么是为什么呢?用if不行用while就可以。

什么是虚假唤醒:是不想唤醒它或者说不确定是否应该唤醒,但是被唤醒了。对程序来说,wait 方法应该卡住当前程序,不应该往后执行;但是实际上并没有被卡住,而是在非预期的时间程序正常执行了,没有程序没有被卡住就是被虚假唤醒了

5、结论

在if条件判断下,他只会判断一次,如果这时候被notify唤醒之后,程序会从当前等待的代码继续执行(也就是进入了if判断之后的代码)。而如果使用while判断,唤醒后会重新执行while循环条件。如果条件成立,就继续wait。因此就出现了上面看到的现象。

好了,又白嫖了一个无聊的小知识,没用但有趣的小知识。

相关文章
|
调度
线程产生的虚假唤醒问题 原因和解决
多个线程并发争抢一个资源会产生线程虚假唤醒问题
151 0
|
调度
【多线程:wait/notify详解】原理及错误用法(虚假唤醒等)
【多线程:wait/notify详解】原理及错误用法(虚假唤醒等)
223 0
求求你,别再用wait和notify了!(5)
求求你,别再用wait和notify了!(5)
111 0
|
Java
求求你,别再用wait和notify了!(1)
Condition 是 JDK 1.5 中提供的用来替代 wait 和 notify 的线程通讯方法,那么一定会有人问:为什么不能用 wait 和 notify 了? 哥们我用的好好的。老弟别着急,听我给你细说... 之所以推荐使用 Condition 而非 Object 中的 wait 和 notify 的原因有两个: 使用 notify 在极端环境下会造成线程“假死”; Condition 性能更高。 接下来怎们就用代码和流程图的方式来演示上述的两种情况。
125 0
求求你,别再用wait和notify了!(1)
求求你,别再用wait和notify了!(3)
求求你,别再用wait和notify了!(3)
97 0
求求你,别再用wait和notify了!(3)
|
调度
求求你,别再用wait和notify了!(4)
求求你,别再用wait和notify了!(4)
108 0
求求你,别再用wait和notify了!(4)
求求你,别再用wait和notify了!(2)
求求你,别再用wait和notify了!(2)
123 0
求求你,别再用wait和notify了!(2)
|
8月前
|
Java
JAVA多线程的“心灵感应”:wait()与notify()的秘密
【6月更文挑战第20天】Java多线程中,`wait()`和`notify()`是线程间协作的关键。它们充当线程间的通信桥梁,使得线程能感知对方状态。例如,生产者线程在资源满时`wait()`,消费者线程消费后`notify()`或`notifyAll()`,确保资源有效利用且避免冲突。简化的代码示例展示了这种同步机制,线程通过等待和唤醒操作实现“心灵感应”般的协同工作。
39 3
|
8月前
|
安全 Java
JAVA多线程通信新解:wait()、notify()、notifyAll()的实用技巧
【6月更文挑战第20天】Java多线程中,`wait()`, `notify()`和`notifyAll()`用于线程通信。在生产者-消费者模型示例中,它们确保线程同步。`synchronized`保证安全,`wait()`在循环内防止虚假唤醒,`notifyAll()`避免唤醒单一线程问题。关键技巧包括:循环内调用`wait()`,优先使用`notifyAll()`以保证可靠性,以及确保线程安全和正确处理`InterruptedException`。
80 0
|
9月前
|
Java 调度
并发编程之的虚假唤醒和精准唤醒的详细解析
并发编程之的虚假唤醒和精准唤醒的详细解析
85 0

热门文章

最新文章