生产者-消费者模式
Posted Tao-Coder
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了生产者-消费者模式相关的知识,希望对你有一定的参考价值。
生产者消费者模型
生产者消费者模型具体来讲,就是在一个系统中,存在生产者和消费者两种角色,他们通过内存缓冲区进行通信,生产者生产消费者需要的资料,消费者把资料做成产品。生产消费者模式如下图。
举例说明:
- 你把信写好——相当于生产者制造数据
- 你把信放入邮筒——相当于生产者把数据放入缓冲区
- 邮递员把信从邮筒取出——相当于消费者把数据取出缓冲区
- 邮递员把信拿去邮局做相应的处理——相当于消费者处理数据
生产者消费者模型实现
生产者是一堆线程,消费者是另一堆线程,内存缓冲区可以使用List数组队列,数据类型只需要定义一个简单的类就好,关键是如何处理多线程之间的协作。换句话说如何利用该模型实现线程通信。
在这个模型中,最关键就是内存缓冲区为空的时候消费者必须等待,而内存缓冲区满的时候,生产者必须等待。其他时候可以是个动态平衡。值得注意的是多线程对临界区资源的操作时候必须保证在读写中只能存在一个线程,所以需要设计锁的策略。
简单地讲就是:
- 生产者生产数据到缓冲区中,消费者从缓冲区中取数据。
- 如果缓冲区已经满了,则生产者线程阻塞;
- 如果缓冲区为空,那么消费者线程阻塞。
下面我们采用三种方式来实现该模式
方式一:synchronized、wait和notify
package producerConsumer; //wait 和 notify public class ProducerConsumerWithWaitNofity { public static void main(String[] args) { Resource resource = new Resource(); //生产者线程 ProducerThread p1 = new ProducerThread(resource); ProducerThread p2 = new ProducerThread(resource); ProducerThread p3 = new ProducerThread(resource); //消费者线程 ConsumerThread c1 = new ConsumerThread(resource); //ConsumerThread c2 = new ConsumerThread(resource); //ConsumerThread c3 = new ConsumerThread(resource); p1.start(); p2.start(); p3.start(); c1.start(); //c2.start(); //c3.start(); } } /** * 公共资源类 * @author * */ class Resource{//重要 //当前资源数量 private int num = 0; //资源池中允许存放的资源数目 private int size = 10; /** * 从资源池中取走资源 */ public synchronized void remove(){ if(num > 0){ num--; System.out.println("消费者" + Thread.currentThread().getName() + "消耗一件资源," + "当前线程池有" + num + "个"); notifyAll();//通知生产者生产资源 }else{ try { //如果没有资源,则消费者进入等待状态 wait(); System.out.println("消费者" + Thread.currentThread().getName() + "线程进入等待状态"); } catch (InterruptedException e) { e.printStackTrace(); } } } /** * 向资源池中添加资源 */ public synchronized void add(){ if(num < size){ num++; System.out.println(Thread.currentThread().getName() + "生产一件资源,当前资源池有" + num + "个"); //通知等待的消费者 notifyAll(); }else{ //如果当前资源池中有10件资源 try{ wait();//生产者进入等待状态,并释放锁 System.out.println(Thread.currentThread().getName()+"线程进入等待"); }catch(InterruptedException e){ e.printStackTrace(); } } } } /** * 消费者线程 */ class ConsumerThread extends Thread{ private Resource resource; public ConsumerThread(Resource resource){ this.resource = resource; } @Override public void run() { while(true){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } resource.remove(); } } } /** * 生产者线程 */ class ProducerThread extends Thread{ private Resource resource; public ProducerThread(Resource resource){ this.resource = resource; } @Override public void run() { //不断地生产资源 while(true){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } resource.add(); } } }
方式二:lock和condition的await、signalAll
package producerConsumer; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * 使用Lock 和 Condition解决生产者消费者问题 * @author tangzhijing * */ public class LockCondition { public static void main(String[] args) { Lock lock = new ReentrantLock(); Condition producerCondition = lock.newCondition(); Condition consumerCondition = lock.newCondition(); Resource2 resource = new Resource2(lock,producerCondition,consumerCondition); //生产者线程 ProducerThread2 producer1 = new ProducerThread2(resource); //消费者线程 ConsumerThread2 consumer1 = new ConsumerThread2(resource); ConsumerThread2 consumer2 = new ConsumerThread2(resource); ConsumerThread2 consumer3 = new ConsumerThread2(resource); producer1.start(); consumer1.start(); consumer2.start(); consumer3.start(); } } /** * 消费者线程 */ class ConsumerThread2 extends Thread{ private Resource2 resource; public ConsumerThread2(Resource2 resource){ this.resource = resource; //setName("消费者"); } public void run(){ while(true){ try { Thread.sleep((long) (1000 * Math.random())); } catch (InterruptedException e) { e.printStackTrace(); } resource.remove(); } } } /** * 生产者线程 * @author tangzhijing * */ class ProducerThread2 extends Thread{ private Resource2 resource; public ProducerThread2(Resource2 resource){ this.resource = resource; setName("生产者"); } public void run(){ while(true){ try { Thread.sleep((long) (1000 * Math.random())); } catch (InterruptedException e) { e.printStackTrace(); } resource.add(); } } } /** * 公共资源类 * @author tangzhijing * */ class Resource2{ private int num = 0;//当前资源数量 private int size = 10;//资源池中允许存放的资源数目 private Lock lock; private Condition producerCondition; private Condition consumerCondition; public Resource2(Lock lock, Condition producerCondition, Condition consumerCondition) { this.lock = lock; this.producerCondition = producerCondition; this.consumerCondition = consumerCondition; } /** * 向资源池中添加资源 */ public void add(){ lock.lock(); try{ if(num < size){ num++; System.out.println(Thread.currentThread().getName() + "生产一件资源,当前资源池有" + num + "个"); //唤醒等待的消费者 consumerCondition.signalAll(); }else{ //让生产者线程等待 try { producerCondition.await(); System.out.println(Thread.currentThread().getName() + "线程进入等待"); } catch (InterruptedException e) { e.printStackTrace(); } } }finally{ lock.unlock(); } } /** * 从资源池中取走资源 */ public void remove(){ lock.lock(); try{ if(num > 0){ num--; System.out.println("消费者" + Thread.currentThread().getName() + "消耗一件资源," + "当前资源池有" + num + "个"); producerCondition.signalAll();//唤醒等待的生产者 }else{ try { consumerCondition.await(); System.out.println(Thread.currentThread().getName() + "线程进入等待"); } catch (InterruptedException e) { e.printStackTrace(); }//让消费者等待 } }finally{ lock.unlock(); } } }
方式三:BlockingQueue
package producerConsumer; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; //使用阻塞队列BlockingQueue解决生产者消费者 public class BlockingQueueConsumerProducer { public static void main(String[] args) { Resource3 resource = new Resource3(); //生产者线程 ProducerThread3 p = new ProducerThread3(resource); //多个消费者 ConsumerThread3 c1 = new ConsumerThread3(resource); ConsumerThread3 c2 = new ConsumerThread3(resource); ConsumerThread3 c3 = new ConsumerThread3(resource); p.start(); c1.start(); c2.start(); c3.start(); } } /** * 消费者线程 * @author tangzhijing * */ class ConsumerThread3 extends Thread { private Resource3 resource3; public ConsumerThread3(Resource3 resource) { this.resource3 = resource; //setName("消费者"); } public void run() { while (true) { try { Thread.sleep((long) (1000 * Math.random())); } catch (InterruptedException e) { e.printStackTrace(); } resource3.remove(); } } } /** * 生产者线程 * @author tangzhijing * */ class ProducerThread3 extends Thread{ private Resource3 resource3; public ProducerThread3(Resource3 resource) { this.resource3 = resource; //setName("生产者"); } public void run() { while (true) { try { Thread.sleep((long) (1000 * Math.random())); } catch (InterruptedException e) { e.printStackTrace(); } resource3.add(); } } } class Resource3{ private BlockingQueue resourceQueue = new LinkedBlockingQueue(10); /** * 向资源池中添加资源 */ public void add(){ try { resourceQueue.put(1); System.out.println("生产者" + Thread.currentThread().getName() + "生产一件资源," + "当前资源池有" + resourceQueue.size() + "个资源"); } catch (InterruptedException e) { e.printStackTrace(); } } /** * 向资源池中移除资源 */ public void remove(){ try { resourceQueue.take(); System.out.println("消费者" + Thread.currentThread().getName() + "消耗一件资源," + "当前资源池有" + resourceQueue.size() + "个资源"); } catch (InterruptedException e) { e.printStackTrace(); } } }
该模式的优点
1. 解耦
假设生产者和消费者分别是两个类。如果让生产者直接调用消费者的某个方法,那么生产者对消费者就会产生依赖(耦合)。将来如果消费者代码发送变化,可能会影响到生产者。而如果两者都依赖某个缓冲区,两者之间不直接依赖,耦合也就相对降低了。
举个例子,我们去邮局投递信件,如果不使用邮筒(也就是缓冲区),你必须得把 信直接交给邮递员。有同学会说,直接给邮递员不是挺简单的嘛?其实不简单,你必须 得认识谁是邮递员,才能把信给他(光凭身上穿的制服,万一有人假冒,就惨了)。这 就产生和你和邮递员之间的依赖(相当于生产者和消费者的强耦合)。万一哪天邮递员 换人了,你还要重新认识一下(相当于消费者变化导致修改生产者代码)。而邮筒相对 来说比较固定,你依赖它的成本就比较低(相当于和缓冲区之间的弱耦合)。
2.支持并发
由于生产者与消费者是两个独立的并发体,它们之间是通过缓冲区作为桥梁连接,生产者只需要往缓冲区丢数据,就可以继续生产下一个数据,而消费者只需要从缓冲区拿数据即可,这样就不会因为彼此的处理速度而发生阻塞。
接上面的例子,如果我们不使用邮筒,我们就得在邮局等邮递员,直到他回来,我们把信件交给他,这期间我们啥事儿都不能干(也就是生产者阻塞),或者邮递员得挨家挨户问,谁要寄信(相当于消费者轮询)。
2.支持忙闲不均
缓冲区还有另一个好处。如果制造数据的速度时快时慢,缓冲区的好处就体现出来了。当数据制造快时,消费者来不及处理,未处理的数据可以暂时存在缓冲区中,等生产者的制造速度慢下来,消费者再慢慢处理掉。
为了充分复用,我们再拿寄信的例子来说事。假设邮递员一次只能带走1000封信。 万一某次碰上情人节(也可能是圣诞节)送贺卡,需要寄出去的信超过1000封,这时 候邮筒这个缓冲区就派上用场了。邮递员把来不及带走的信暂存在邮筒中,等下次过来 时再拿走。
实际应用
在P3版本升级项目中,信息服务器要接收大批量的客户端请求,原来那种串行化的 处理,根本无法及时处理客户端请求,造成信息服务器大量请求堆积,导致丢包异 常严重。之后就采用了生产者消费者模式,在业务请求与业务处理间,建立了一个List 类型的缓冲区,服务端接收到业务请求,就往里扔,然后再去接收下一个业务请求,而 多个业务处理线程,就会去缓冲区里取业务请求处理。这样就大大提高了服务器的相 应速度。
以上是关于生产者-消费者模式的主要内容,如果未能解决你的问题,请参考以下文章