java-多线程(二)-生产者与消费者

什么是生产者消费者模型?

举个例子:以DOTA来说,辅助进行拉野,大哥收野,辅助就是生产者,大哥就是消费者,辅助拉一波,大哥清一波.这两个配合起来,就是生产者与消费者

但是这样效率很低,毕竟一波野怪还不够让大哥挪动的,所以我们对其进行优化,加入队列的思想.

生产者消费者模型队列:

以一个饭店为例,厨师做一个菜,服务员传一个菜,但是在这个时候出现了问题,如果生产速度过快,消费能力过弱,或者生产能力过弱,消费能力过强,有一方就需要等待,会造成资源的浪费.

下边就要引入生产者与消费者模型的队列,上边的例子,厨师就是生产者,服务员就是消费者,但是两者不直接进行通讯,而是通过一个缓冲区,生产者对数据进行生产之后将其放入缓冲区,而消费者直接从缓冲区取数据,两者并不直接进行通讯.

而这个缓冲区,就通过阻塞队列来实现, 队列的特性就是先进先出,所以不会出现后生产的数据被先拿走,或者先生产的数据没有被使用的情况.

下面通过一个例子来了解一下简单的生产者消费者模型.

这里我们使用的队列为BlockingQueue,线程安全,也就是同一时刻,只允许一个线程对其进行访问,不会同时出现多个线程访问的情况,它将会阻塞线程.

先上代码:

实体类:
package producerConsumer;

//测试用实体类
public class Food {

    private String name;//食物名称

    private double price;//食物价格

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

    @Override
    public String toString() {
        return "Food{" +
                "name='" + name + '\'' +
                ", price=" + price +
                '}';
    }

    public String getName() {
        return name;
    }

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

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }
}

消费者
public class ConsumerRunner implements Runnable {


    @Override
    public void run() {
        try {

            while (!Thread.currentThread().isInterrupted()) {

                System.out.println(foodBlockingQueue.take().toString());//从队列中拿取元素
                TimeUnit.MILLISECONDS.sleep(200);

            }
        } catch (InterruptedException e) {
            System.out.println("interrupted");
        }

        System.out.println("Ending of ConsumerRunner");

    }
}

生产者:
public class ProducerRunner implements Runnable {



    //生产数据并且向队列添加
    @Override
    public void run() {

        try {
            while (!Thread.currentThread().isInterrupted()) {

                Food food = new Food("鸡肉", 50.25);//新建food实体
                System.out.println("生产成功");
                foodBlockingQueue.put(food);//填入队列

                TimeUnit.MILLISECONDS.sleep(200);


            }
        }catch(InterruptedException e){

            System.out.println("interrupted");

        }
        System.out.println("Ending of ProducerRunner");
    }
}

测试用类:

	//测试用类
public class pcTest {

    public static BlockingQueue<Food> foodBlockingQueue = new LinkedBlockingQueue<Food>() ;//静态队列方便全局调用



    public static void main(String[] args) throws InterruptedException {



        ExecutorService exec = Executors.newCachedThreadPool();//创建线程池

        exec.execute(new ProducerRunner());//启动生产者线程

        exec.execute(new ConsumerRunner());//启动消费者线程

        TimeUnit.MILLISECONDS.sleep(2000);//延缓main()线程时间,体现效果

        exec.shutdownNow();//终止exec旗下所有线程.	

    }
}

结果:

生产成功
Food{name='鸡肉', price=50.25}
生产成功
Food{name='鸡肉', price=50.25}
生产成功
Food{name='鸡肉', price=50.25}
生产成功
Food{name='鸡肉', price=50.25}
生产成功
Food{name='鸡肉', price=50.25}
生产成功
Food{name='鸡肉', price=50.25}
生产成功
Food{name='鸡肉', price=50.25}
生产成功
Food{name='鸡肉', price=50.25}
生产成功
Food{name='鸡肉', price=50.25}
生产成功
Food{name='鸡肉', price=50.25}
interrupted
interrupted
Ending of ProducerRunner
Ending of ConsumerRunner

下面说一个稍微复杂的情况,当你既是消费者,又是生产者的时候,并且需要通过你消费的数据从而加工生成新的数据,举个例子比如菜农是生产者,饭店是消费者, 而饭店又是菜品的生产者,下级的消费者是服务员(顾客点菜传菜用),这个时候单向的队列就不能满足需求了.

下面看一个经典的吐司blockingQueue案例

实体类吐司:

public class Toast {

    public enum Status{DRY,BUTTERED,JAMMED}//面包状态,白面包,抹了黄油的,抹了果酱的

    private Status status= Status.DRY;//默认是白面包

    private final int id;

    public Toast(int id){
        this.id=id;//吐司ID
    }


    //抹黄油!
    public void butter(){

        this.status=Status.BUTTERED;
    }
    
    //抹果酱!
    public void jammed(){
        this.status=Status.JAMMED;
    }


    public Status getStatus() {
        return status;
    }

    public int getId() {
        return id;
    }

    @Override
    public String toString() {
        return "Toast{" +
                "status=" + status +
                ", id=" + id +
                '}';
    }
}

做吐司的:

public class Toaster implements Runnable {

    private ToastQueue toastQueue;//一个吐司队列

    private int count=0;//计数器

    private Random random =new Random(47);//随机

    public Toaster(ToastQueue tq){
        toastQueue = tq;//通过有参构造传入队列
    }



    @Override
    public void run() {

        try {
            while (!Thread.currentThread().isInterrupted()) {
                TimeUnit.MILLISECONDS.sleep(100 + random.nextInt(500));//做吐司

                Toast t = new Toast(count++);//从0开始 每生产一个编号+1 把编号传入当做吐司ID

                System.out.println(t.toString());

                toastQueue.put(t);


            }
        }catch(InterruptedException e){

            System.out.println("线程中断");

        }

        System.out.println("吐司制作结束,没面粉了!");

    }
}

给白面包抹黄油的:

//黄油怪,涂抹黄油
public class Butterer implements Runnable  {

    private ToastQueue dryQueue,butteredQueue;//建立白面包队列和黄油面包队列

    //这里解释一下,因为这里抹黄油的既是白吐司的消费者,也黄油面包的生产者,况且,黄油面包还可以被别人消费,所以这里要声明两个队列


    public Butterer(ToastQueue dryQueue,ToastQueue butteredQueue) {

        this.dryQueue =dryQueue;
        this.butteredQueue = butteredQueue;
    }

    @Override
    public void run() {

        try{
            while(!Thread.currentThread().isInterrupted()){

                Toast toast = dryQueue.take();//拿白面包
                toast.butter();//抹黄油
                TimeUnit.MILLISECONDS.sleep(200);//模拟抹黄油
                System.out.println(toast.toString());//打印状态
                butteredQueue.put(toast);//将黄油吐司放入队列.
            }
        }catch(InterruptedException e){

            System.out.println("黄油中断");

        }

        System.out.println("抹黄油结束");

    }
}

给黄油吐司抹果酱的:

public class janmmer implements  Runnable {

    private ToastQueue butteredQueue,finishQueue;//黄油面包,抹果酱结束.

    public janmmer(ToastQueue butteredQueue, ToastQueue finishQueue) {
        this.butteredQueue = butteredQueue;
        this.finishQueue = finishQueue;
    }



    @Override
    public void run() {

        try{

            while(!Thread.currentThread().isInterrupted()){

               Toast t =  butteredQueue.take();//拿黄油吐司
                t.jammed();//抹果酱
                TimeUnit.MILLISECONDS.sleep(200);
                System.out.println(t.toString());//打印状态
                finishQueue.put(t);//扔进结束队列

            }

        }catch(InterruptedException e){

            System.out.print("抹果酱中断");
        }

        System.out.println("抹果酱程序结束");




    }
}

吃吐司的人:

public class Eater implements Runnable {

    private ToastQueue finishQueue;

    private int counter=0;

    public Eater(ToastQueue finishQueue) {
        this.finishQueue = finishQueue;
    }

    @Override
    public void run() {

        try{
            while(!Thread.currentThread().isInterrupted()){
                Toast t =finishQueue.take();//拿面包
                if(t.getId()!=counter++||t.getStatus()!=Toast.Status.JAMMED){
                    //如果面包编号不对(顺序上错了),或者面包的没有涂果酱,会报错

                    System.out.println("错误: 这不是我要的面包!,他不是果酱面包!");
                }else{

                    System.out.println("真,真香"+t.toString());
                }
            }
            } catch(InterruptedException e) {
            System.out.println("中断,不吃了");
        }
        System.out.println("用餐结束");
    }
}

吐司队列,方面书写,语义明确:

public class ToastQueue extends LinkedBlockingQueue {}

测试类:

	public class ToastTest {


    public static void main(String[] args) throws InterruptedException {

        ToastQueue dryToast =new ToastQueue();
        ToastQueue butteredToast = new ToastQueue();
        ToastQueue finToast=new ToastQueue();
        //创建三个吐司队列
        ExecutorService  exec = Executors.newCachedThreadPool();//创建线程池
        exec.execute(new Toaster(dryToast));//做吐司
        exec.execute(new Butterer(dryToast,butteredToast));//抹黄油
        exec.execute(new janmmer(butteredToast,finToast));//抹果酱
        exec.execute(new Eater(finToast));//吃

        TimeUnit.MILLISECONDS.sleep(2000);

        exec.shutdownNow();


    }
}

测试结果:
Toast{status=DRY, id=0}
Toast{status=DRY, id=1}
Toast{status=BUTTERED, id=0}
Toast{status=JAMMED, id=0}
真,真香Toast{status=JAMMED, id=0}
Toast{status=BUTTERED, id=1}
Toast{status=DRY, id=2}
Toast{status=JAMMED, id=1}
真,真香Toast{status=JAMMED, id=1}
Toast{status=BUTTERED, id=2}
Toast{status=JAMMED, id=2}
真,真香Toast{status=JAMMED, id=2}
Toast{status=DRY, id=3}
Toast{status=BUTTERED, id=3}
Toast{status=JAMMED, id=3}
真,真香Toast{status=JAMMED, id=3}
Toast{status=DRY, id=4}
中断,不吃了
用餐结束
黄油中断
抹果酱中断抹果酱程序结束
线程中断
吐司制作结束,没面粉了!
抹黄油结束

由于BlockingQueue是阻塞队列,并且由于队列的特性,我们可以看出食客是按照顺序吃的,这样不需要线程同步,中间每个对象传递都是唯一顺序,从队列中取也只能按照队列的顺序来.

这个例子准确来说不是特别严谨,体会其中生产者与消费者的关系,其中也没有对中断后的数据进行后续清理操作,主要表达消费者同时是生产者时,通过队列来交互信息的思想.

posted @ 2018-05-25 14:44  CurryRice  阅读(349)  评论(0编辑  收藏  举报