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碗!!!
分析
时序细节(对应前几次循环):
-
初始:
count=10,foodFlag=0。两个线程启动,假设Foodie先获得锁,发现没面条,wait()并释放锁。 -
厨师第一次生产:
Cook获得锁,设置foodFlag=1,notifyAll,退出同步块。此时准备打印,但还未执行打印语句。 -
吃货第一次消费:
Foodie被唤醒,竞争到锁,发现foodFlag=1,消费:count变为 9,打印“还能再吃9碗”,设置foodFlag=0,notifyAll,释放锁。 -
厨师第一次打印:此时
Cook才执行System.out.println,输出“厨师做了一碗面条”。(这条日志对应第一次生产,但打印在消费日志之后) -
厨师第二次生产:
Cook再次循环,获得锁(因为foodFlag=0),设置foodFlag=1,notifyAll,退出同步块,准备打印。 -
厨师第二次打印:在
Foodie竞争到锁之前,Cook的打印语句先执行,输出第二个“厨师做了一碗面条”。(这条日志对应第二次生产) -
吃货第二次消费:
Foodie获得锁,消费,打印“还能再吃8碗”。
因此,连续两个厨师日志分别对应第一次生产(但打印延迟到了消费之后)和第二次生产(打印在消费之前)。这不是虚假唤醒,而是打印位置不当导致的日志顺序错乱。业务逻辑本身是正确的(生产一次、消费一次交替进行)。
如何让日志更清晰?
将打印语句移入同步块内,放在 foodFlag 修改之后,可以保证“生产日志”与“实际生产动作”原子地出现,

7498

被折叠的 条评论
为什么被折叠?



