8、生产者与消费者案例



厨师制作食物,放在取餐口让服务员取走食物,两个角色最终共享的数据即是食物


首先模拟生产消费的过程,未加同步,所以会出现两方面的问题:

1、食物与对应描述混乱

2、本来应该每样菜品生成5次,取出的菜品数量对不上(桑拿滑肉片7次,锅包肉3次)

package com.chocus.demo1;

public class ProductCustomerDemo {

  public static void main(String[] args) {
    Food food = new Food();
    Customers c = new Customers(food);
    Producter p = new Producter(food);
    Thread t1 = new Thread(c);
    Thread t2 = new Thread(p);
    t1.start();
    t2.start();
  }

}


/**
 * 消费者
 * 
 * @author Chocus
 *
 */
class Customers implements Runnable {

  private Food food;

  public Customers(Food food) {
    this.food = food;
  }

  @Override
  public void run() {
    for (int i = 0; i < 10; i++) {
      food.get();
    }
  }

}


/**
 * 生产者
 * 
 * @author Chocus
 *
 */
class Producter implements Runnable {

  private Food food;

  public Producter(Food food) {
    this.food = food;
  }

  @Override
  public void run() {
    // 模拟生成食物,单数一种,双数一种
    for (int i = 0; i < 10; i++) {
      if (i % 2 == 0) {
        food.set("锅包肉", "酸甜可口");
      } else {
        food.set("桑拿滑肉片", "锤儿很喜欢吃");
      }
    }
  }

}


/**
 * 食物:生产者和消费者共享的数据
 * 
 * @author Chocus
 *
 */
class Food {

  /**
   * 名称
   */
  private String name;
  /**
   * 描述
   */
  private String desc;

  /**
   * 生产食物
   */
  public void set(String name, String desc) {
    this.name = name;
    // 模拟生成食物的过程
    try {
      Thread.sleep(500);
    } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    this.desc = desc;
  }

  /**
   * 取走食物
   */
  public void get() {
    // 模拟取走食物的过程
    try {
      Thread.sleep(500);
    } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    System.out.println("取走食物" + name + ",食物信息:" + desc);
  }

  public Food(String name, String desc) {
    this.name = name;
    this.desc = desc;
  }

  public Food() {

  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public String getDesc() {
    return desc;
  }

  public void setDesc(String desc) {
    this.desc = desc;
  }

}

结果

取走食物桑拿滑肉片,食物信息:酸甜可口
取走食物锅包肉,食物信息:锤儿很喜欢吃
取走食物桑拿滑肉片,食物信息:酸甜可口
取走食物锅包肉,食物信息:锤儿很喜欢吃
取走食物桑拿滑肉片,食物信息:酸甜可口
取走食物锅包肉,食物信息:锤儿很喜欢吃
取走食物桑拿滑肉片,食物信息:酸甜可口
取走食物桑拿滑肉片,食物信息:酸甜可口
取走食物桑拿滑肉片,食物信息:酸甜可口
取走食物桑拿滑肉片,食物信息:锤儿很喜欢吃

经过简单分析可以发现,问题出在生产食物的set方法和取走食物的get方法线程不安全造成

那么在这两个方法上加同步后根据结果发现:

1、每样菜品生成5次的问题解决了,这证明加了同步后,不存在线程安全问题,及不会出现生产一半被取走的情况

2、发现出现null的情况,即还未生产菜品,菜品就被取走了

package com.chocus.demo1;

public class ProductCustomerDemo {

  public static void main(String[] args) {
    Food food = new Food();
    Customers c = new Customers(food);
    Producter p = new Producter(food);
    Thread t1 = new Thread(c);
    Thread t2 = new Thread(p);
    t1.start();
    t2.start();
  }

}


/**
 * 消费者
 * 
 * @author Chocus
 *
 */
class Customers implements Runnable {

  private Food food;

  public Customers(Food food) {
    this.food = food;
  }

  @Override
  public void run() {
    for (int i = 0; i < 10; i++) {
      food.get();
    }
  }

}


/**
 * 生产者
 * 
 * @author Chocus
 *
 */
class Producter implements Runnable {

  private Food food;

  public Producter(Food food) {
    this.food = food;
  }

  @Override
  public void run() {
    // 模拟生成食物,单数一种,双数一种
    for (int i = 0; i < 10; i++) {
      if (i % 2 == 0) {
        food.set("锅包肉", "酸甜可口");
      } else {
        food.set("桑拿滑肉片", "锤儿很喜欢吃");
      }
    }
  }

}


/**
 * 食物:生产者和消费者共享的数据
 * 
 * @author Chocus
 *
 */
class Food {

  /**
   * 名称
   */
  private String name;
  /**
   * 描述
   */
  private String desc;

  /**
   * 生产食物
   */
  public synchronized void set(String name, String desc) {
    this.name = name;
    // 模拟生成食物的过程
    try {
      Thread.sleep(500);
    } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    this.desc = desc;
  }

  /**
   * 取走食物
   */
  public synchronized void get() {
    // 模拟取走食物的过程
    try {
      Thread.sleep(500);
    } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    System.out.println("取走食物" + name + ",食物信息:" + desc);
  }

  public Food(String name, String desc) {
    this.name = name;
    this.desc = desc;
  }

  public Food() {

  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public String getDesc() {
    return desc;
  }

  public void setDesc(String desc) {
    this.desc = desc;
  }

}
结果

取走食物null,食物信息:null
取走食物锅包肉,食物信息:酸甜可口
取走食物桑拿滑肉片,食物信息:锤儿很喜欢吃
取走食物锅包肉,食物信息:酸甜可口
取走食物锅包肉,食物信息:酸甜可口
取走食物锅包肉,食物信息:酸甜可口
取走食物桑拿滑肉片,食物信息:锤儿很喜欢吃
取走食物桑拿滑肉片,食物信息:锤儿很喜欢吃
取走食物桑拿滑肉片,食物信息:锤儿很喜欢吃
取走食物桑拿滑肉片,食物信息:锤儿很喜欢吃

根据问题分析可以发现,菜品还未生产就被取走,那么就需要先生产菜品,生产完毕后再取走


package com.chocus.demo1;

public class ProductCustomerDemo {

  public static void main(String[] args) {
    Food food = new Food();
    Customers c = new Customers(food);
    Producter p = new Producter(food);
    Thread t1 = new Thread(c);
    Thread t2 = new Thread(p);
    t1.start();
    t2.start();
  }

}


/**
 * 消费者
 * 
 * @author Chocus
 *
 */
class Customers implements Runnable {

  private Food food;

  public Customers(Food food) {
    this.food = food;
  }

  @Override
  public void run() {
    for (int i = 0; i < 10; i++) {
      food.get();
    }
  }

}


/**
 * 生产者
 * 
 * @author Chocus
 *
 */
class Producter implements Runnable {

  private Food food;

  public Producter(Food food) {
    this.food = food;
  }

  @Override
  public void run() {
    // 模拟生成食物,单数一种,双数一种
    for (int i = 0; i < 10; i++) {
      if (i % 2 == 0) {
        food.set("锅包肉", "酸甜可口");
      } else {
        food.set("桑拿滑肉片", "锤儿很喜欢吃");
      }
    }
  }

}


/**
 * 食物:生产者和消费者共享的数据
 * 
 * @author Chocus
 *
 */
class Food {

  /**
   * 名称
   */
  private String name;
  /**
   * 描述
   */
  private String desc;

  /**
   * 定义生产消费标记: 
   *    true:可以消费,不能生产;
   *    false:可以生产,不能消费;
   *    注意:开始时要设置为不能消费!!!!
   */
  private boolean flag = false;

  /**
   * 生产食物
   */
  public synchronized void set(String name, String desc) {
    // 生产的食物还没有被取走,那么先进入等待状态,停止生产
    if (flag) {
      try {
        this.wait();
      } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      }
    }
    // 模拟生成食物的过程
    this.name = name;
    try {
      Thread.sleep(500);
    } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    this.desc = desc;
    // 产品生产完毕后通知消费者来消费
    flag = true;
    // notifyAll:唤醒休眠中的其他所有线程
    // notify:唤醒休眠中的某一个线程(随机)
    this.notifyAll();
  }

  /**
   * 取走食物
   */
  public synchronized void get() {

    // 当没有食物的食物不能消费,进入等待状态
    if (!flag) {
      try {
        this.wait();
      } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      }
    }

    // 模拟取走食物的过程
    try {
      Thread.sleep(500);
    } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    System.out.println("取走食物" + name + ",食物信息:" + desc);
    // 取走食物后通知生产者生产食物
    flag = false;
    // notifyAll:唤醒休眠中的其他所有线程
    // notify:唤醒休眠中的某一个线程(随机)
    this.notifyAll();
  }

  public Food(String name, String desc) {
    this.name = name;
    this.desc = desc;
  }

  public Food() {

  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public String getDesc() {
    return desc;
  }

  public void setDesc(String desc) {
    this.desc = desc;
  }

}
结果 5:5

取走食物锅包肉,食物信息:酸甜可口
取走食物桑拿滑肉片,食物信息:锤儿很喜欢吃
取走食物锅包肉,食物信息:酸甜可口
取走食物桑拿滑肉片,食物信息:锤儿很喜欢吃
取走食物锅包肉,食物信息:酸甜可口
取走食物桑拿滑肉片,食物信息:锤儿很喜欢吃
取走食物锅包肉,食物信息:酸甜可口
取走食物桑拿滑肉片,食物信息:锤儿很喜欢吃
取走食物锅包肉,食物信息:酸甜可口
取走食物桑拿滑肉片,食物信息:锤儿很喜欢吃

现在加大力度,两个生产者,两个消费者

public class ProductCustomerDemo {

  public static void main(String[] args) {
    Food food = new Food();
    Customers c = new Customers(food);
    Producter p = new Producter(food);
    Thread t1 = new Thread(c);
    Thread t2 = new Thread(p);
    Thread t3 = new Thread(c);
    Thread t4 = new Thread(p);
    t1.start();
    t2.start();
    t3.start();
    t4.start();
  }

}

结果 10:10

取走食物锅包肉,食物信息:酸甜可口
取走食物锅包肉,食物信息:酸甜可口
取走食物桑拿滑肉片,食物信息:锤儿很喜欢吃
取走食物桑拿滑肉片,食物信息:锤儿很喜欢吃
取走食物锅包肉,食物信息:酸甜可口
取走食物桑拿滑肉片,食物信息:锤儿很喜欢吃
取走食物锅包肉,食物信息:酸甜可口
取走食物锅包肉,食物信息:酸甜可口
取走食物桑拿滑肉片,食物信息:锤儿很喜欢吃
取走食物桑拿滑肉片,食物信息:锤儿很喜欢吃
取走食物锅包肉,食物信息:酸甜可口
取走食物桑拿滑肉片,食物信息:锤儿很喜欢吃
取走食物锅包肉,食物信息:酸甜可口
取走食物锅包肉,食物信息:酸甜可口
取走食物桑拿滑肉片,食物信息:锤儿很喜欢吃
取走食物桑拿滑肉片,食物信息:锤儿很喜欢吃
取走食物锅包肉,食物信息:酸甜可口
取走食物桑拿滑肉片,食物信息:锤儿很喜欢吃
取走食物锅包肉,食物信息:酸甜可口
取走食物桑拿滑肉片,食物信息:锤儿很喜欢吃

一个生产者,两个消费者

public static void main(String[] args) {
    Food food = new Food();
    Customers c = new Customers(food);
    Producter p = new Producter(food);
    Thread t1 = new Thread(c);
    Thread t2 = new Thread(p);
    Thread t3 = new Thread(c);
    t1.start();
    t2.start();
    t3.start();
  }
结果 5:5,一个人生产,两个人排队领取食物
取走食物锅包肉,食物信息:酸甜可口
取走食物桑拿滑肉片,食物信息:锤儿很喜欢吃
取走食物锅包肉,食物信息:酸甜可口
取走食物桑拿滑肉片,食物信息:锤儿很喜欢吃
取走食物锅包肉,食物信息:酸甜可口
取走食物桑拿滑肉片,食物信息:锤儿很喜欢吃
取走食物锅包肉,食物信息:酸甜可口
取走食物桑拿滑肉片,食物信息:锤儿很喜欢吃
取走食物锅包肉,食物信息:酸甜可口
取走食物桑拿滑肉片,食物信息:锤儿很喜欢吃

两个生产者,一个消费者

public static void main(String[] args) {
    Food food = new Food();
    Customers c = new Customers(food);
    Producter p = new Producter(food);
    Thread t1 = new Thread(c);
    Thread t2 = new Thread(p);
    Thread t4 = new Thread(p);
    t1.start();
    t2.start();
    t4.start();
  }

结果 6:4,两个人竞争生产,一个人来领取,可以看出,又出现了线程安全问题,即两个人同时在生成中

取走食物锅包肉,食物信息:酸甜可口
取走食物锅包肉,食物信息:酸甜可口
取走食物桑拿滑肉片,食物信息:锤儿很喜欢吃
取走食物桑拿滑肉片,食物信息:锤儿很喜欢吃
取走食物锅包肉,食物信息:酸甜可口
取走食物锅包肉,食物信息:酸甜可口
取走食物桑拿滑肉片,食物信息:锤儿很喜欢吃
取走食物桑拿滑肉片,食物信息:锤儿很喜欢吃
取走食物锅包肉,食物信息:酸甜可口
取走食物锅包肉,食物信息:酸甜可口

根据上述问题,可以找到解决方案

方案一:即在单双数生产食物过程中加上同步即可,但是此种方案可能出现死锁。。。可能。。。所以推荐第二种方案

/**
 * 生产者
 * 
 * @author Chocus
 *
 */
class Producter implements Runnable {

  private Food food;

  public Producter(Food food) {
    this.food = food;
  }

  @Override
  public void run() {
    product();
  }

  private synchronized void product() {
    // 模拟生成食物,单数一种,双数一种
    for (int i = 0; i < 10; i++) {
      if (i % 2 == 0) {
        food.set("锅包肉", "酸甜可口");
      } else {
        food.set("桑拿滑肉片", "锤儿很喜欢吃");
      }
    }
  }

}
结果 5:5

取走食物锅包肉,食物信息:酸甜可口
取走食物桑拿滑肉片,食物信息:锤儿很喜欢吃
取走食物锅包肉,食物信息:酸甜可口
取走食物桑拿滑肉片,食物信息:锤儿很喜欢吃
取走食物锅包肉,食物信息:酸甜可口
取走食物桑拿滑肉片,食物信息:锤儿很喜欢吃
取走食物锅包肉,食物信息:酸甜可口
取走食物桑拿滑肉片,食物信息:锤儿很喜欢吃
取走食物锅包肉,食物信息:酸甜可口
取走食物桑拿滑肉片,食物信息:锤儿很喜欢吃


方案二:生产者和消费者都只唤醒一个线程,即让一个人来做,做好了通知一个人来领

/**
 * 食物:生产者和消费者共享的数据
 * 
 * @author Chocus
 *
 */
class Food {

  /**
   * 名称
   */
  private String name;
  /**
   * 描述
   */
  private String desc;

  /**
   * 定义生产消费标记: 
   *    true:可以消费,不能生产;
   *    false:可以生产,不能消费;
   *    注意:开始时要设置为不能消费!!!!
   */
  private boolean flag = false;

  /**
   * 生产食物
   */
  public synchronized void set(String name, String desc) {
    // 生产的食物还没有被取走,那么先进入等待状态,停止生产
    if (flag) {
      try {
        this.wait();
      } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      }
    }
    // 模拟生成食物的过程
    this.name = name;
    try {
      Thread.sleep(500);
    } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    this.desc = desc;
    // 产品生产完毕后通知消费者来消费
    flag = true;
    // notifyAll:唤醒休眠中的其他所有线程
    // notify:唤醒休眠中的某一个线程(随机)
    this.notify();
  }

  /**
   * 取走食物
   */
  public synchronized void get() {

    // 当没有食物的食物不能消费,进入等待状态
    if (!flag) {
      try {
        this.wait();
      } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      }
    }

    // 模拟取走食物的过程
    try {
      Thread.sleep(500);
    } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    System.out.println("取走食物" + name + ",食物信息:" + desc);
    // 取走食物后通知生产者生产食物
    flag = false;
    // notifyAll:唤醒休眠中的其他所有线程
    // notify:唤醒休眠中的某一个线程(随机)
    this.notify();
  }

  public Food(String name, String desc) {
    this.name = name;
    this.desc = desc;
  }

  public Food() {

  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public String getDesc() {
    return desc;
  }

  public void setDesc(String desc) {
    this.desc = desc;
  }

}

结果:5:5

取走食物锅包肉,食物信息:酸甜可口
取走食物桑拿滑肉片,食物信息:锤儿很喜欢吃
取走食物锅包肉,食物信息:酸甜可口
取走食物桑拿滑肉片,食物信息:锤儿很喜欢吃
取走食物桑拿滑肉片,食物信息:锤儿很喜欢吃
取走食物锅包肉,食物信息:酸甜可口
取走食物锅包肉,食物信息:酸甜可口
取走食物桑拿滑肉片,食物信息:锤儿很喜欢吃
取走食物桑拿滑肉片,食物信息:锤儿很喜欢吃
取走食物锅包肉,食物信息:酸甜可口

根据以上知识总结:






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值