生产者消费者

前言


生产者消费者模式属于一种经典的多线程协作的模式,弄清生产者消费者问题能够让我们对于多线程编程有更深刻的理解,下面,为大家分享一个生产者消费者的案例。

一、案例描述
这里以快递为例,假设有一个快递柜,用来存快递,然后有快递员和取件人,快递员往快递柜里存快递,取件人从快递柜中取走快递。快递员作为生产者,取件人作为消费者,当两者在一个时间段同时进行多次自己的操作时,很明显这就是多线程编程的生产者消费者实例了。在这里,我们希望快递员(生产者)存入一个快递,取件人(消费者)就拿走一个快递,如果快递还没有被取走,那么生产者应该等待,而如果快递柜里没有快递,则消费者应该等待。

首先来明确一下,这个案例我们需要准备:

快递柜类(Box):包含一个成员变量,表示快递的序号,并提供存快递和取快递的操作方法
生产者类(Producer):实现Runnable接口,包含存快递的方法
消费者类(Customer):实现Runnable接口,包含取快递的方法
测试类(BoxDemo):测试类按如下步骤实现这个案例
(1) 创建快递柜对象作为共享数据区域
(2) 创建生产者,把快递柜对象作为参数传递至构造方法,因为生产者需要完成存快递的操作
(3)创建消费者,把快递柜作为对象传递至构造方法,因为消费者需要完成取快递的操作
(4)创建两个线程,将生产者和消费者对象分别作为参数传递至线程的构造方法,然后启动线程
下面是具体实现:

二、创建快递柜


代码如下:
————————————————

public class Box {
    //定义成员变量表示第几个快递(快递序号)
    private int express;
    //定义一个成员变量用于表示快递柜的状态
    private boolean flag = false;

    //存快递
    public synchronized void put(int express) {
        //如果有快递,那么快递员应该等待取件人来取快递
        if (flag) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //如果没有快递,那么快递员就存入快递
        this.express = express;
        System.out.println("快递员将第" + this.express + "个快递存入了快递柜");
        //别忘了存完修改快递柜的状态
        flag = true;
        //修改完快递柜状态后,唤醒其他在等待的线程
        notifyAll();
    }

    //取快递
    public synchronized void get() {
        //如果有快递,那么取件人就取走快递
        if (flag) {
            System.out.println("取件人取出了第" + this.express + "个快递");
            flag = false;
            notifyAll();
        } else {
            //没有快递,那么取件人就等待
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

说明:

如之前的分析,我们创建了一个Box类当做快递柜,除了表示快递序号的成员变量以外和对应的存快递、取快递方法外,还包括一个用来标记快递柜状态的变量,因为线程执行时需要这个标记来判断是该执行还是等待。存快递和取快递的方法都加上了sychronized变成了同步方法,因为用于等待的wait()方法和唤醒的notifyAll()方法要在sychronized块中使用,否则会抛出 IllegalMonitorStateException异常而无法执行。

三、创建生产者类

代码如下:
————————————————
 

public class Producer implements Runnable{
    private Box b;

    public Producer(Box b){
        this.b = b;
    }
    @Override
    public void run() {
        for(int i = 1 ;i<11;i++){
            b.put(i);
        }
    }
}

说明:

快递员当做生产者类,它实现了Runnable接口,重写了run()方法,并且有一个Box类型的成员变量,和一个以这个成员变量为参数的构造方法,因为在这个类中要调用存快递的操作。在这里,run()方法里一共存入了10次快递。

四、创建消费者类

代码如下:

public class Customer implements Runnable{
    private Box b ;

    public Customer(Box b){
        this.b = b;
    }
    @Override
    public void run() {
        while(true){
            b.get();
        }
    }
}

说明:

同生产者类一样,消费者(取件人)类也实现了Runnable接口,重写了run()方法,同样有一个Box类型的成员变量,和一个以这个成员变量为参数的构造方法,因为这个类里会调用取快递的操作。由于能取快递的次数是由生产者(快递员)存入多少快递决定的,所以这里我们直接用while循环就好了。

五、测试类


在测试类中,我们分别创建快递柜、生产者和消费者的对象,将快递柜对象作为参数分别传入生产者和消费者创建时的构造方法。然后创建两个线程,分别将生产者和消费者对象作为构造方法的参数传递,最后启动线程,观察结果。

代码如下:
 

public class BoxDemo {
    public static void main(String[] args) {
        //创建快递柜对象
        Box box = new Box();
        //创建生产者和消费者对象
        Producer p = new Producer(box);
        Customer c = new Customer(box);
        //创建两个线程
        Thread t1 = new Thread(p,"生产者线程");
        Thread t2 = new Thread(c,"消费者线程");
        //启动线程
        t1.start();
        t2.start();
    }
}

生产者消费者_第1张图片

 可以看到,快递员和取件人有序地完成了10个快递的存和取

// 店员类:负责进货和售货
class Clerk{
    private int num = 0; //店里当前的货物量

    public synchronized void get() { //店员进货  每次进货一个(生产者)
        if(num >= 10) {
            System.out.println(Thread.currentThread().getName()+" 库存已满,无法进货");
            try {
                this.wait();
                System.out.println(Thread.currentThread().getName()+" wait后剩余步骤");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } else {
            System.out.println(Thread.currentThread().getName()+" : "+ (++num));
            this.notifyAll();
        }
    }

    public synchronized void sale() { //店员卖货 每次卖掉一个货(消费者)
        if(num<=0) {
            System.out.println(Thread.currentThread().getName()+" 库存已空,无法卖货");
            try {
                this.wait();
                System.out.println(Thread.currentThread().getName()+" wait后剩余步骤");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }else {
            System.out.println(Thread.currentThread().getName()+" : "+(--num));
            this.notifyAll();
        }
    }
}

// 生产者 可以有很多生产者卖货给这个店员
class Producer implements Runnable{
    private Clerk clerk;

    public Producer(Clerk clerk) {
        this.clerk=clerk;
    }

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

//消费者:可以很多消费者找店员买货
class Consumer implements Runnable{
    private Clerk clerk;

    public Consumer(Clerk clerk) {
        this.clerk=clerk;
    }

    @Override
    public void run() {
        for (int i = 0; i<20; i++) {
            clerk.sale();
        }
    }
}

public class TestProductorAndConsumer {
    public static void main(String[] args) {
        Clerk clerk=new Clerk();

        Producer producer=new Producer(clerk);
        Consumer consumer=new Consumer(clerk);

        new Thread(producer,"生产者A").start();
        new Thread(consumer,"消费者B").start();
    }
}

你可能感兴趣的:(线程)