为啥厨师和服务员会打架?--多线程通信问题之生产者和消费者

概念

是一个典型的多线程通信问题,当生产者生产时,消费者进行休眠;当生产者结束生产,唤醒消费者进行消费,生产者休眠。

在线程调用的类中需要使用同步方法关键字synchronized,这是为了防止两个线程同时使用方法,调用同一个对象中的属性。

实例代码

代码中主要有两个线程,一个叫Cook,一个叫Waiter,他们各自可以调用名为Food的类对象中的一个方法。

Cook和Waiter实现线程的方法是继承Thread类,重写run方法

Cook是厨师,英语中职业类名词唯一不加-er或-or后缀的那个。

问题代码

Cook打算做100份菜,分别是50份玉米糁子和50份宫保鸡丁;Waiter负责在Cook做好以后端走这饭菜。

因为盘子只有一个,所以Cook在Waiter把盘子端回来之前不能做下一份菜,那Cook要怎么办呢?

public class ThreadDemo {
    public static void main(String[] args) throws InterruptedException {
        Food f = new Food();
        new Cook(f).start();
        new Waiter(f).start();
    }

    public static class Cook extends Thread{
        Food f;
        Cook(Food f){
            this.f = f;
        }

        @Override
        public void run() {
            for(int i =0; i<100; i++){
                if(i%2 ==0){
                    try {
                        f.setNameAndTaste("玉米糁子","甜");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }else{
                    try {
                        f.setNameAndTaste("宫保鸡丁","香辣");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    static class Waiter extends Thread{
        Food f;
        Waiter(Food f){
            this.f = f;
        }

        @Override
        public void run(){
            for(int i =0; i<100; i++){
                try {
                    f.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                try {
                    f.get(f);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }       
    }



    public static class Food extends Thread{
        private String name;
        private String taste;

        private boolean flag = true;

        public void setNameAndTaste(String name,  String taste) throws InterruptedException {
            if(flag){
                this.name = name;
                sleep(100);
                this.taste = taste;
                flag =false;
                this.notifyAll();
                this.wait();
            }

        }

        public void get(Food f) throws InterruptedException {
            if(!flag){
                System.out.println("服务员端走了"+f.name+",味道是:"+f.taste);
                flag = true;
                this.notifyAll();
                this.wait();
            }
        }
    }
}

第一次的代码不成功,报错如下

Exception in thread "Thread-1" java.lang.IllegalMonitorStateException
  at java.base/java.lang.Object.notifyAll(Native Method)
  at ThreadDemo.ThreadDemo$Food.setNameAndTaste(ThreadDemo.java:79)
  at ThreadDemo.ThreadDemo$Cook.run(ThreadDemo.java:25)
服务员端走了玉米糁子,味道是:甜
Exception in thread "Thread-2" java.lang.IllegalMonitorStateException
  at java.base/java.lang.Object.notifyAll(Native Method)
  at ThreadDemo.ThreadDemo$Food.get(ThreadDemo.java:89)
  at ThreadDemo.ThreadDemo$Waiter.run(ThreadDemo.java:55)

更改代码

更改后的代码如下,注意看注释

   //食物
    static class Food{
        private String name;
        private String taste;

        //true表示可以生产
        private boolean flag = true;
        //setNameAndTaste这个方法需要加上关键字synchronized
        public synchronized void setNameAndTaste(String name,  String taste){
            if(flag){
                this.name = name;
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                this.taste = taste;
                flag =false;
                this.notifyAll();
                try {
                    this.wait();
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
        }
        //get也要加上关键字synchronized
        public synchronized void get() {
            if(!flag){
                System.out.println("服务员端走了"+name+",味道是:"+taste);
                flag = true;
                this.notifyAll();
                try {
                    this.wait();
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
        }
    }

需要加关键字的原因在于:

厨师和服务员两个线程调用Food,如果不限制同步方法,厨师会在没有盘子的情况下开始做饭,并导致出错(虽然我也不知道内部究竟怎么回事,为啥只有一个盘子。。)

上一篇:k8s部署vsftp 支持多线程下载


下一篇:flashfxpFTP链接显示PASV、列表错误