zoukankan      html  css  js  c++  java
  • 生产者消费者问题

    这是一个面试经常被问到的问题,很多问题都可以转化为这个模型。

    什么是生产者与消费者问题?举个例子,我们去吃自助餐,在自助餐的一个公共区域放着各种食物,消费者需要就自行挑选,当食物被挑没的时候,大家就等待,等候厨师做出更多再放到公共区域内供大家挑选;当公共区域食物达到一定数量,不能再存放的时候,此时没有消费者挑选,厨师此时等待,等公共区域有地方再存放食物时,再开始生产。这就是一个生产者与消费者问题。

    根据这个例子,我们可以模拟一下场景,我们从这个例子中,显然看出我们需要制造一个公共区域,而且这个公共区域是有容量限制的,需要模拟各种食物,同时还需要模拟几个厨师也就是生产者,最后再模拟几个消费者。

    首先呢,我们创建一个产品Product类,这个类就代表食物的模板,厨师们就生产这种类型的食物,类里面定义食物的ID和name这两个属性,代码如下:

    public class Product {
        private int id;
        private String name;
        public Product(int id, String name) {
            super();
            this.id = id;
            this.name = name;
        }
        @Override
        public String toString() {
            return "Product :id:"+id+",name:"+name;
        }
        public int getId() {
            return id;
        }
        public void setId(int id) {
            this.id = id;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
    }

    接下来,我们创建那个公共区域,也就是一个容器,容器要么用数组保存,要么用集合,这里我们用集合LinkedList,然后呢,我们要清楚我们这个容器只能被创建一个,也就是公共区域只有一个,我们放也是放在这里面,取也是从这里面取,这里采用单例模式来保证只能创建一个容器实例,容器里面再定义放和取的方法,我们要加锁保证放和取的同步,以免发生线程安全问题,同时还要定义容器的最大容量,再放和取的同步方法里面,我们要判断是否容器内的食物超出了容器的最大容量,如果放的时候大于等于最大容量了,就不能再往里面放了,这时候厨师们(生产者)要等待;取的时候也一样,看是否容器内的食物个数为0,为0消费者们就要等待,最后我们在容器里面定义一个检查容器容量的方法,后面单独开启一个线程调用此方法,实时监测容器内食物的个数,一直在0-10之间,就说明我们写的代码没有问题,代码如下:

    public class Container {
        //单例模式,保证只能创建一个容器实例
        private static Container instance = null;
        private Container(){}
        public static Container getInstance(){
            if(instance == null){
                instance = new Container();
            }
            return instance;
        }
        private LinkedList<Product> list = new LinkedList<>();//容器
        private int MAX = 10;//容器的最大容量
        //放食物
        public synchronized void putProduct(Product product){
            while(list.size()>=MAX){
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            list.addLast(product);//把新生产的食物放在最后
            notifyAll();
        }
        //取食物
        public synchronized Product getProduct(){
            while(list.size()<=0){
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            notifyAll();
            return list.removeFirst();//先取出放在这里时间最长的那个
        }
        //检查容器内食物个数
        public int checkSize(){
            return list.size();
        }
    }

    接下来就是生产者(厨师)线程,这个线程里面就是不停的生产食物,然后放入容器里面供消费者挑选,更形象一点,生产食物需要固定的时间,我们让线程每生产一个食物睡眠一段时间,时间为固定的。

    代码如下:

    public class ProducerThread implements Runnable{
        Container container = null;
        public ProducerThread(Container container) {
            this.container = container;
        }
        @Override
        public void run() {
            int i = 0;
            while(true){
                Product product = new Product(++i, "产品"+i);
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                container.putProduct(product);
            }
        }
    }

    有了生产者,接下来就是消费者线程了,该线程里面消费者不停的挑选产品,消费产品,每消费一个食物也需要一定的时间,而且这个对于每一个人时间是不一定的,所以呢我们就取1200以内的随机数,进行睡眠。

    代码如下:

    public class CustomThread implements Runnable{
        Container container = null;
        public CustomThread(Container container) {
            this.container = container;
        }
        @Override
        public void run() {
            while(true){
                Product p = container.getProduct();
                try {
                    Thread.sleep((int)(Math.random()*1200));
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                System.out.println("消费了一个产品"+p);
            }
        }
    }

    在生产者与消费者线程里面,我们都定义了构造器,用来接收容器对象,这里面,并没有对容器进行初始化,就是为了保证容器的唯一性。

    所需要的我们都定义完毕,最后定义一个测试类Go,测试类里面,创建2个生产者线程,10个消费者线程,并创建一个容器传给生产者和消费者,同时,另外开启一个线程,每隔300毫秒进行输出容器内食物的数量,如果食物数量一直在0到10之间,说明我们的程序没有问题,为了让打印看的更清晰,这里不用System.out.println打印,而是用显示为红色的System.err.println进行输出。

    代码如下:

    public class Go {
        public static void main(String[] args) {
            Container container = Container.getInstance();
            for (int i = 0; i < 2; i++) {
                new Thread(new ProducerThread(container)).start();; 
            }
            for (int i = 0; i < 10; i++) {
                new Thread(new CustomThread(container)).start();; 
            }
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while(true){
                        try {
                            Thread.sleep(300);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.err.println(container.checkSize());
                    }
                }
            }).start();
        }
    }

    整个生产者和消费者就完成了。

    这里面一定要思考,代码的同步,容器的唯一以及对容器容量的控制,之间是怎么进行通讯的。

  • 相关阅读:
    设计模式代理模式
    设计模式建造者模式(生成器模式)
    SQL SERVER SA 无法登陆的解决方法
    [转]ASP.NET编程技巧10则
    键盘伪码
    MICROSOFT和ADOBE干上了?
    WINDOWS2003域控制器禁止U盘
    Dameware在Winxp下权限配置
    FTP操作
    WinForm 鼠标在页面无操作时页面关闭
  • 原文地址:https://www.cnblogs.com/javatalk/p/9931619.html
Copyright © 2011-2022 走看看