java thread wait notify 虚假唤醒

package org.example;

public class Main {
    public static void main(String[] args) {
        Cook c = new Cook();
        Foodie f=new Foodie();

        c.setName("厨师");
        f.setName("吃货");

        c.start();
        f.start();


        }

}

package org.example;

public class Desk {

    //0:没有面条; 1:有面条
    public static int foodFlag = 0;
    //总个数
    public static int count=10;

    //锁对象
    public static Object lock = new Object();


}

package org.example;
public class Cook extends Thread{

    @Override
    public void run() {
        while (true){
            synchronized (Desk.lock){
                if(Desk.count ==0){
                    break;
                }else{
                    //判断桌子上是否有食物
                    if(Desk.foodFlag ==1){
                        try{
                            Desk.lock.wait();
                        }
                        catch (InterruptedException e){
                            e.printStackTrace();
                        }
                    }else {
                        //如果没有,就制作食物
                        //System.out.println("厨师做了一碗面条");
                        //修改桌子上的食物状态
                        Desk.foodFlag = 1;
                        //叫醒等待的消费者开吃
                        Desk.lock.notifyAll();
                    }
                }
            }
            System.out.println("厨师做了一碗面条");
        }
    }
}

package org.example;

public class Foodie extends Thread{
    @Override
    public void run() {
        while (true){
            synchronized (Desk.lock){
                if(Desk.count==0){
                    break;
                }else{
                    //先判断桌子上是否有面条
                    if (Desk.foodFlag ==0){
                        //如果没有,就等待
                        try {
                            Desk.lock.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }else{
                        Desk.count--;
                        //如果有,就开吃
                        System.out.println("吃货在吃面条,还能再吃"+ Desk.count+"碗!!!");
                        //log.info("吃货在吃面条,还能再吃"+ Desk.count+"碗!!!");
                        Desk.lock.notifyAll();
                        Desk.foodFlag =0;
                    }
                }
            }
        }
    }
}

厨师做了一碗面条
吃货在吃面条,还能再吃9碗!!!
厨师做了一碗面条
吃货在吃面条,还能再吃8碗!!!
厨师做了一碗面条
厨师做了一碗面条
吃货在吃面条,还能再吃7碗!!!
厨师做了一碗面条
厨师做了一碗面条
吃货在吃面条,还能再吃6碗!!!
厨师做了一碗面条
厨师做了一碗面条
吃货在吃面条,还能再吃5碗!!!
厨师做了一碗面条
厨师做了一碗面条
吃货在吃面条,还能再吃4碗!!!
厨师做了一碗面条
厨师做了一碗面条
吃货在吃面条,还能再吃3碗!!!
厨师做了一碗面条
吃货在吃面条,还能再吃2碗!!!
厨师做了一碗面条
厨师做了一碗面条
吃货在吃面条,还能再吃1碗!!!
厨师做了一碗面条
厨师做了一碗面条
吃货在吃面条,还能再吃0碗!!!
厨师做了一碗面条

您这次提供的代码中,Cook 和 Foodie 都使用了 if 而不是 while 来检查等待条件。运行结果中出现了大量连续的“厨师做了一碗面条”,这正是虚假唤醒 + if 条件判断导致的典型问题。

运行结果分析

  • 吃货日志正好 10 条(从 9 碗到 0 碗),说明实际消费次数正确。

  • 厨师日志远多于 10 条,且多次连续出现(例如“厨师做了一碗面条”连续两三条后才跟一条吃货日志)。

  • 程序最终正常退出(count 减到 0 后两个线程都 break 退出)。

为什么会出现大量多余的厨师日志?

关键在于 Cook 类的代码结构:

java

synchronized (Desk.lock) {
    if (Desk.count == 0) break;
    else {
        if (Desk.foodFlag == 1) {
            Desk.lock.wait();   // 可能虚假唤醒
        } else {
            Desk.foodFlag = 1;
            Desk.lock.notifyAll();
        }
    }
}
// 日志在同步块外部
System.out.println("厨师做了一碗面条");

虚假唤醒发生时

  1. 厨师线程在 wait() 时被 JVM 自发唤醒(没有 notify)。

  2. 由于使用的是 if,线程不会重新检查 foodFlag == 1 是否仍然成立,直接退出 if 块。

  3. 然后退出同步块,必然执行 System.out.println("厨师做了一碗面条")

  4. 但此时 foodFlag 很可能还是 1(因为并没有真正的吃货来消费),厨师并没有真正做面条(没有执行 else 分支,foodFlag 没有被再次设置为 1)。然而日志却打印了。

连续多次虚假唤醒:同一个厨师线程可能被连续多次虚假唤醒,每次都会打印一条日志,而 foodFlag 始终为 1,导致一连串的“厨师做了一碗面条”日志。直到某个时刻,吃货线程获得 CPU 时间,消费掉面条(foodFlag 变为 0,count 减 1,notifyAll),厨师才能进入正常的 else 分支真正做一次面条。

因此,厨师日志的数量 = 真正做面条的次数(10 次) + 虚假唤醒的次数。您的输出中厨师日志大约有 20 多条,说明发生了十几次虚假唤醒。

为什么使用 if 会导致这个问题?

  • if 只检查一次条件。如果线程在条件不满足时 wait,被唤醒后(无论是正常还是虚假)不会重新检查条件,直接继续执行后面的代码。

  • 虚假唤醒使得线程在不满足条件的情况下继续执行,导致错误的日志打印(甚至更严重的状态错误)。

正确的做法(必须使用 while

java

synchronized (Desk.lock) {
    if (Desk.count == 0) break;
    while (Desk.foodFlag == 1) {   // while 循环
        Desk.lock.wait();
    }
    // 此时 foodFlag 一定为 0
    Desk.foodFlag = 1;
    System.out.println("厨师做了一碗面条");  // 日志放在同步块内
    Desk.lock.notifyAll();
}

同样,Foodie 类中也要改为:

java

while (Desk.foodFlag == 0) {
    Desk.lock.wait();
}

使用 while 后

  • 每次唤醒(包括虚假唤醒)都会重新检查 foodFlag,如果不满足条件(例如还是 1)就继续 wait,不会错误地打印日志或执行后续操作。

  • 日志放在同步块内,保证与状态修改原子输出,不会出现多余的日志。

总结

  • 您当前的 if 写法没有防御虚假唤醒,导致厨师线程被多次虚假唤醒后错误地打印了多条“做面条”日志。

  • 虽然程序没有崩溃(因为虚假唤醒没有修改共享数据),但日志完全不可信,且如果逻辑更复杂(例如在 wait() 后直接修改状态)就会引发严重错误。

  • 必须将 if 改为 while,这是 Java 并发编程的强制规范,不是可选项。

修复后,输出将会严格交替:

text

厨师做了一碗面条
吃货在吃面条,还能再吃9碗!!!
厨师做了一碗面条
吃货在吃面条,还能再吃8碗!!!
...
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值