在并发编程中使用生产者和消费者模式能够解决绝大多数并发问题。该模式通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度。

为什么要使用生产者和消费者模式

在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这种生产消费能力不均衡的问题,所以便有了生产者和消费者模式。

什么是生产者消费者模式

生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。

这个阻塞队列就是用来给生产者和消费者解耦的。纵观大多数设计模式,都会找一个第三者出来进行解耦,如工厂模式的第三者是工厂类,模板模式的第三者是模板类。在学习一些设计模式的过程中,如果先找到这个模式的第三者,能帮助我们快速熟悉一个设计模式。

生产者消费者模式实战

放入20个苹果和取出20个苹果。生产者每次向篮子里放五个苹果,消费者依次取出篮子里的苹果。取出五个苹果后,生产者才能继续往篮子里放。

一、先创建生产者和消费者

1.创建生产者

新建一个生产者继承Thread,目的是向篮子里面放苹果(run方法里面)

class Productor extends Thread
{
    private Basket basket=null;
    public Productor(Basket basket)
    {
        super();
        this.basket=basket;
    }
    public void run()
    {
        basket.pushApple();//向篮子里面放苹果
    }

}

2.创建消费者

新建一个消费者继承Thread,目的是从篮子里取苹果

class Consumer extends Thread
{
    private Basket basket=null;
    public Consumer(Basket basket)
    {
        super();
        this.basket=basket;
    }
    public void run()
    {
        basket.popApple();//从篮子里面取苹果
    }

}

二、实现放苹果和取苹果方法

1.创建篮子类和苹果类

放苹果和取苹果都需要操作篮子和苹果这两个对象,所以我们先创建篮子和苹果类

苹果类很简单,就是一个简单的javaBean。包含苹果的id属性。

class Apple{
    private int id;
    Apple(int id)
    {
        this.id=id;
    }
    public String toString()
    {
        return "苹果"+id;
    }
}

篮子类稍微有点复杂,主要目的就是实现放苹果和取苹果的逻辑

先用一个集合(这里用的是链表集合)存放苹果,用链表集合有个好处,既可以实现先放入的先拿出来,又可以实现先放入的后拿出来。

然后用两个循环分别实现放入20个苹果和取出20个苹果

class Basket{
    private LinkedList<Apple> basket=new LinkedList<Apple>();
    public synchronized void pushApple()
    {
        for(int i=1;i<=20;i++)
        {
            Apple apple=new Apple(i);
            push(apple);
        }
    }
    public synchronized void popApple()
    {
        for(int i=1;i<=20;i++)
        {
            pop();
        }
    }
    public void push(Apple apple)
    {
    ...
    }
    private void pop()
    {
    ...
    }
}

2.实现生产者放苹果和消费者取苹果

在(1)中可以看到,放苹果pushApple()方法和取苹果popApple中加入了synchronized,实现了这两者的同步方法。即放的时候不能取,取的时候不能放。

接下来看放苹果和取苹果的逻辑

如果篮子里的苹果数不为5.那么就每隔500ms放入一个苹果。即将苹果对象加入到篮子集合中。

当放入5个苹果后,让这个生产者开始等待并唤醒消费者。

此时消费者出场了,消费者开始消费苹果,每隔500ms取出一个。当全部取出时,消费者开始等待并唤醒生产者。

这样一直循环下去,直到放20个苹果和取20个苹果完成后,结束任务。

public void push(Apple apple)
    {
        if(basket.size()==5)
        {
            try{
                wait();
            }catch(InterruptedException e)
            {
                System.out.println("放入苹果异常"+Thread.currentThread().isInterrupted());
            }
        }
        try{
            Thread.sleep(500);
        }catch(InterruptedException e)
        {
            System.out.println("放入苹果异常"+Thread.currentThread().isInterrupted());
        }
        basket.addFirst(apple);
        System.out.println("存放"+apple.toString());
        notifyAll();
    }
    private void pop()
    {
        if(basket.size()==0)
        {
            try{
                wait();
            }catch(InterruptedException e)
            {
                e.printStackTrace();
            }
        }
        try{
            Thread.sleep(500);
        }catch(InterruptedException e)
        {
            e.printStackTrace();
        }
        Apple apple=basket.removeFirst();
        System.out.println("吃掉"+apple.toString());
        notifyAll();
    }

三、测试

创建生产者和消费者对象,并开始生产和消费

public class ProductConsumerDemo {
    public static void main(String[] args) throws InterruptedException
    {
        Basket basket=new Basket();
        Productor productor=new Productor(basket);
        Consumer consumer=new Consumer(basket);
        productor.start();        
        consumer.start();
 
    }

}

20180609154638753.jpg

四、知识拓展

上面代码中在实现wait()方法中会捕捉InterruptedException异常,那么什么情况下会抛出这个异常呢?

这个异常是中断异常,调用线程的interrupt()方法就会抛出这个异常。

比如下面

Thread.sleep(10000);
productor.interrupt();

这个就可以实现10秒后让product也就是生产者抛出中断异常。

20180609155640829.jpg

最后修改:2019 年 04 月 30 日 03 : 30 PM
如果觉得我的文章对你有用,请随意赞赏