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;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Cook extends Thread{

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

                }
            }
            //System.out.println("厨师做了一碗面条   "+Desk.foodFlag);
            System.out.println("厨师做了一碗面条   ");
            //厨师做完面条后,还没来得及打印上面这句话,就被吃货线程先抢到锁,执行消费了;
            //消费后打印上面这句话,Desk.foodFlag是0
        }
    }
}

package org.example;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Foodie extends Thread{
    @Override
    public void run() {
        while (true){
            synchronized (Desk.lock){
                if(Desk.count==0){
                    break;
                }else{
                    //先判断桌子上是否有面条
                    while (Desk.foodFlag ==0){
                        //如果没有,就等待
                        try {
                            Desk.lock.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                    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碗!!!

分析

时序细节(对应前几次循环):

  1. 初始count=10foodFlag=0。两个线程启动,假设 Foodie 先获得锁,发现没面条,wait() 并释放锁。

  2. 厨师第一次生产Cook 获得锁,设置 foodFlag=1notifyAll,退出同步块。此时准备打印,但还未执行打印语句。

  3. 吃货第一次消费Foodie 被唤醒,竞争到锁,发现 foodFlag=1,消费:count 变为 9,打印“还能再吃9碗”,设置 foodFlag=0notifyAll,释放锁。

  4. 厨师第一次打印:此时 Cook 才执行 System.out.println,输出“厨师做了一碗面条”。(这条日志对应第一次生产,但打印在消费日志之后)

  5. 厨师第二次生产Cook 再次循环,获得锁(因为 foodFlag=0),设置 foodFlag=1notifyAll,退出同步块,准备打印。

  6. 厨师第二次打印:在 Foodie 竞争到锁之前,Cook 的打印语句先执行,输出第二个“厨师做了一碗面条”。(这条日志对应第二次生产)

  7. 吃货第二次消费Foodie 获得锁,消费,打印“还能再吃8碗”。

因此,连续两个厨师日志分别对应第一次生产(但打印延迟到了消费之后)和第二次生产(打印在消费之前)。这不是虚假唤醒,而是打印位置不当导致的日志顺序错乱。业务逻辑本身是正确的(生产一次、消费一次交替进行)。

如何让日志更清晰?

将打印语句移入同步块内,放在 foodFlag 修改之后,可以保证“生产日志”与“实际生产动作”原子地出现,

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值