当前位置: 首页 > 科技观察

Java生产者消费者问题

时间:2023-03-13 00:07:43 科技观察

简介  生产者消费者问题是线程模型中的一个经典问题:生产者和消费者在同一时间段共享同一个存储空间,如下图所示,生产者空间存储数据,消费者访问数据。如果没有协调,可能会出现以下情况:生产者消费者图  存储空间满了,生产者占用了,消费者等着生产者放手生产者从产品中取出产品空间,生产者等待消费者消费产品,从而将产品添加到空间中。互相等待,造成死锁。  生产者消费者问题是研究多线程程序绕不开的经典问题之一。它描述了有一个缓冲区作为仓库。拿起产品。生产者/消费者问题的解决方案可以分为两类:  (1)使用某种机制来保护生产者和消费者之间的同步;  (2)生产者与消费者之间建立管道。  第一种方法效率更高,容易实现。代码的可控性较好,是常用的模式。第二种流水线缓冲区不易控制,传输的数据对象不易封装等,实用性不强。因此本文只介绍同步机制实现的生产者/消费者问题。  同步问题的核心是:如何保证同一个资源在多个线程并发访问时的完整性。一种常见的同步方法是使用信号或锁定机制来确保资源在任何时候最多被一个线程访问。Java语言在多线程编程中实现了完全的对象化,对同步机制提供了良好的支持。Java中一共有四种方法支持同步,其中前三种是同步方法,一种是管道方法。(1)wait()/notify()方法(2)await()/signal()方法(3)BlockingQueue阻塞队列方法(4)PipedInputStream/PipedOutputStream本文只介绍最常用的前两种,第三种和第四条暂时不讨论,有兴趣的读者可以自行去网上寻找答案。1.wait()/notify()方法  wait()/nofity()方法是基类Object的两个方法,也就是说所有的Java类都会有这两个方法,这样我们就可以提供任何对象实现一种同步机制。  wait()方法:当缓冲区满/空时,生产者/消费者线程停止自己的执行,放弃锁,让自己进入等待状态,让其他线程执行。  notify()方法:当生产者/消费者将产品放入/取出缓冲区时,向其他等待线程发送可执行通知,同时放弃锁使自己进入等待状态.代码实现:1.仓库类importjava.util.LinkedList;/***仓库类Storage实现缓冲区**@authorzcr*/publicclassStorage{//仓库***存储容量privatefinalintMAX_SIZE=100;//仓库存储的载体privateLinkedListlist=newLinkedList();/***生产num个产品*@paramnum生产的产品数量*/publicvoidproduce(intnum){//同步代码段synchronized(list){//如果剩余仓库容量不足while(list.size()+num>MAX_SIZE){System.out.println("【生产数量】:"+num+"\t【库存量】:"+list.size()+"\t暂时不能执行生产任务!");try{//因为不满足条件,生产被阻塞inti=1;i<=num;++i){list.add(newObject());}System.out.println("【已经生产的产品数量】:"+num+"\t【当前存储容量is]:"+list.size());list.notifyAll();}}/***consumenumproducts*@paramnumconsumedproductquantity*/publicvoidconsume(intnum){//同步代码段synchronized(list){//若仓库存量不足while(list.size()getList(){returnlist;}publicvoidsetList(LinkedListlist){this.list=list;}publicintgetMAX_SIZE(){returnMAX_SIZE;}}2.Producer/***Producer类Producer继承线程类Thread***@authorzcr**/publicclassProducerextendsThread{//每次生产的产品数量privateintnum;//放置的仓库privateStoragestorage;//构造函数,设置仓库publicProducer(Storagestorage){this.storage=storage;}//线程运行functionpublicvoidrun(){produce(num);}//调用仓库Storage的生产函数publicvoidproduce(intnum){storage.produce(num);}//get/set方法publicintgetNum(){returnnum;}publicvoidsetNum(intnum){这。num=num;}publicStoragegetStorage(){returnstorage;}publicvoidsetStorage(Storagestorage){this.storage=storage;}}3.Consumer/***Consumer类Consumer继承线程类Thread***@authorzcr**/publicclassConsumerextendsThread{//每次消耗的产品数量privateintnum;//仓库privateStoragestorage;//构造函数,设置仓库publicConsumer(Storagestorage){this.storage=storage;}//线程运行函数publicvoidrun(){consume(num);}//调用生产函数仓库存储publicvoidconsume(intnum){storage.consume(num);}//获取/设置方法){this.storage=storage;}}4.测试类/***测试类Test*@authorzcr**/publicclassTest{publicstaticvoidmain(String[]args){//仓库对象Storagestorage=newStorage();//生产者对象Producerp1=newProducer(storage);Producerp2=newProducer(storage);Producerp3=newProducer(storage);Producerp4=newProducer(storage);Producerp5=newProducer(storage);Producerp6=newProducer(storage);Producerp7=newProducer(storage);//消费者对象Consumerc1=newConsumer(storage);Consumerc2=newConsumer(storage);Consumerc3=newConsumer(storage);//设置生产数量y生产者产品p1.setNum(10);p2.setNum(10);p3.setNum(10);p4.setNum(10);p5.setNum(10);p6.setNum(10);p7.setNum(80);//设置消费品消费数量c1.setNum(50);c2.setNum(20);c3.setNum(30);//线程开始执行c1.start();c2.start();c3.start();p1.start();p2。start();p3.start();p4.start();p5.start();p6.start();p7.start();}}5、结果:【消费商品数量】:50【库存】:0暂时无法执行生产任务!【商品消耗数量】:20【库存】:0暂时无法执行生产任务!【已生产商品数量】:10【当前库存量】:10【需消耗商品数量】:20【库存】:10暂时无法执行生产任务!【商品消耗数量】:50【库存】:10暂时无法执行生产任务!【已生产商品数量】:10【当前库存量为】:20【已生产商品数量】:10【当前库存量为】:30【待消费商品数量】:50库存量】:30暂时无法执行生产任务![已消耗商品数量]:20【当前存储容量为】:10【已生产商品数量】:10【当前存储容量为】:20【已生产商品数量】:10【当前存储容量】:30【消耗商品数量】:50【库存】:30暂时无法执行生产任务!【已生产商品数量】:10【当前入库数量】:40【消耗商品数量】:30【当前入库数量】:10【待消耗商品数量】:50【库存数量】:10暂时无法执行生产任务!【已生产商品数量】:80【当前入库数量】:90【已消费商品数量】:50【当前入库数量】:40  看完上面的代码,你有一个了解wait()/notify()方法实现的同步。你可能对为什么publicvoidproduce(intnum);感到不舒服。和publicvoidconsume(intnum);方法在Storage类中定义。解决方法,为什么不直接在生产者类Producer和消费者类Consumer中实现这两个方法,而是调用Storage类中的实现呢?冷静,稍后会有解释。我们先下去吧。2.await()/signal()方法  JDK5.0之后,Java提供了更加健壮的线程处理机制,包括同步、锁、线程池等,可以实现更细粒度的线程控制。await()和signal()是用于同步的两种方法。它们的功能与wait()/nofity()基本相同,完全可以替代它们,只是直接挂钩了新引入的锁机制Lock。具有更大的灵活性。通过调用Lock对象的newCondition()方法,将条件变量绑定到一个锁对象上,从而控制并发程序访问竞争资源的安全性。  我们看代码:    只需要更新存储类Storage的代码,Producer、Consumer、Test类的代码不用改。仓库类importjava.util.LinkedList;importjava.util.concurrent.locks.Condition;importjava.util.concurrent.locks.Lock;importjava.util.concurrent.locks.ReentrantLock;/***仓库类Storage实现缓冲区***@authorMONKEY.D.MENG2011-03-15**/publicclassStorage{//仓库***存储容量privatefinalintMAX_SIZE=100;//仓库存储的载体privateLinkedListlist=newLinkedList();//加锁privatefinalLocklock=newReentrantLock();//仓库满条件变量privatefinalConditionfull=lock.newCondition();//仓库空条件变量privatefinalConditionempty=lock.newCondition();//生产num个产品publicvoidproduce(intnum){//获取Lock锁。lock();//如果仓库剩余容量不足while(list.size()+num>MAX_SIZE){System.out.println("【待生产商品数量】:"+num+"/t【库存数量】:"+list.size()+"/t暂时无法执行生产任务!");try{//因为不满足条件,pr生产被阻塞full.await();}catch(InterruptedExceptione){e.printStackTrace();}}//当满足生产条件时,生产num个产品for(inti=1;i<=num;++i){list.add(newObject());}System.out.println("【已生产的产品数量】:"+num+"/t【当前存储容量为】:"+list.size());//唤醒所有其他线程full.signalAll();empty.signalAll();//释放锁lock.unlock();}//消费num个产品publicvoidconsume(intnum){//获取锁lock.lock();//如果存储容量仓库数量不足while(list.size()getList(){returnlist;}publicvoidsetList(LinkedListlist){this.list=list;}}结果:【消耗品数量】:50/t【库存】:0/t生产任务暂时无法执行![已生产产品数量]:10/t[当前库存量]:10[生产产品数量]:10/t[当前库存量]:20[待消耗产品数量]:30/t【库存】:20/t暂时无法执行生产任务!【已消耗品数】:20/t【现存量】:0【已生产品数】:10/t【现存量】:10【已产品数】:10/t【现存量】is】:20【消耗品数】:50/t【库存】:20/t生产任务暂时无法执行!【已生产产品数量】:10/t【当前库容为】:30【已生产生产产品数量]:10/t[当前入库量]:40[生产产品数量]:80/t[库存]:40/t暂时无法执行生产任务!【消耗品数量】:30/t【当前入库量为】:10【消耗品数量】:50/t【库存量】:10/t暂时无法执行生产任务![已生产产品数量]:80/t[当前存储量]:90【消耗产品数量】:50/t【当前存储量】:40  所以我们知道我需要定义publicvoidproduce(整数);和publicvoidconsume(intnum);方法,在生产者类Producer和消费者类Consumer中调用Storage类中的实现。在不影响原有架构设计、不修改其他服务层代码的情况下,将可能的变化集中在一个类中。总结  这两种方法的原理是一样的,都是锁住独占空间,阻塞和唤醒线程,第一种方法比较传统,第二种方法比较快。  Thanks:感谢您的耐心阅读!