生产者-消费者问题,也成为有界缓冲区问题。两个线程共享一个公共的固定大小的缓冲区。生产者将消息放入缓冲区,消费者从缓冲区取出消息。
1)缓冲区未满时,生产者生产消息;缓冲区满了就等待
2)缓冲区不为空或量足时,消费者消费;为空或量少就等待
3)消费者发现量不足时,通知生产者
4)生产者发现仓库已满,通知消费者
生产者消费者Java提供了多种实现方式
(1)Object的wait()/notify()方法
(2)Conditon的await()/signal()方法
(3)BlockingQueue阻塞队列方法
(4)PipedInputStream/PipedOutputStream方法
1、Object的wait()/notify()方法
一、定义仓库
class StoreHouse
{
int realNum;
int maxNum = 60;
StoreHouse(int realNum)
{
this.realNum = realNum;
}
synchronized void consume(int needNum) throws Exception
{//不满足消费条件就等待
while(needNum>realNum)
{
System.out.println("需要多消费:"+(needNum-realNum)+" 库存不足,请等待,库存为:"+realNum);
wait();
}
realNum-=needNum;
System.out.println("消费了:"+needNum+" 库存为:"+realNum);
notifyAll();//唤醒其他所有线程
}
synchronized void produce(int needNum) throws Exception
{//不满足生产条件就等待
while(needNum+realNum>maxNum)
{
System.out.println("会多生产了:"+(needNum+realNum-maxNum)+" 请等待,库存为:"+realNum);
wait();
}
realNum+=needNum;
System.out.println("生产了:"+needNum+" 库存为:"+realNum);
notifyAll();
}
}
二、定义消费者线程
class Consumer extends Thread
{
private int needNum;
private StoreHouse sh;
Consumer(int needNum,StoreHouse sh)
{
this.needNum=needNum;
this.sh=sh;
}
@Override
public void run()
{
// TODO Auto-generated method stub
try {
sh.consume(needNum);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
三、定义生产者线程
class Producer extends Thread
{
private int needNum;
private StoreHouse sh;
Producer(int needNum,StoreHouse sh)
{
this.needNum=needNum;
this.sh=sh;
}
@Override
public void run()
{
try {
sh.produce(needNum);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
4、测试程序
public class Produce_consume
{
public static void main(String[] args)
{
StoreHouse sh=new StoreHouse(20);
Consumer consumer=new Consumer(10,sh);
Producer producer=new Producer(20,sh);
Consumer consumer1=new Consumer(25,sh);
Producer producer1=new Producer(30,sh);
Consumer consumer2=new Consumer(35,sh);
Producer producer2=new Producer(40,sh);
Consumer consumer3=new Consumer(45,sh);
Producer producer3=new Producer(50,sh);
consumer.start();
producer.start();
consumer1.start();
producer1.start();
consumer2.start();
producer2.start();
consumer3.start();
producer3.start();
}
}
测试结果:
为了使程序清晰运行,设置的消费和生产参数都不一样。我们发现消费45没有被运行。尽管使用了进程同步,这个程序还是由缺陷的,也就是当消费者和生产者消费和生产的量不一样时,如果没有生产者生产,或生产者生产不足或超量(不能生产),消费者会一直处于等待的情况。所以,在一般的处理中,都会把消费者和生产者的生产/消费数量设置相等,或者将代码稍微修改,生产者根据库存的剩余大小来设置。
synchronized void produce(int needNum) throws Exception
{
if(needNum+realNum>maxNum)needNum=maxNum-realNum;//根据库存的剩余大小来生产
// while(needNum+realNum>maxNum)
// {
// System.out.println("会多生产了:"+(needNum+realNum-maxNum)+" 库存为:"+realNum);
// wait();
// }
realNum+=needNum;
System.out.println("生产了:"+needNum+" 库存为:"+realNum);
notifyAll();
}
2、Condition的await()/signal()方法
Condition 将 Object 监视器方法(wait
、notify
和 notifyAll
)分解成截然不同的对象,以便通过将这些对象与任意 Lock
实现组合使用,为每个对象提供多个等待 set (wait-set)。其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用。 Condition 实例实质上被绑定到一个锁定上。要为特定 Lock
实例获得 Condition 实例,请使用其 newCondition()
方法。lock将同步互斥控制和等待队列分离,互斥保证在某个时刻只有一个线程访问临界区(lock自己完成),等待队列负责保存被阻塞的线程(condition完成)。
public class ProducerConsumerProblem
{
public static void main(String[] args)
{
Storage store=new Storage(2);//初始化仓库数量为0
Producer1 producer=new Producer1(1,store);
Consumer1 consumer=new Consumer1(2,store);
Producer1 producer1=new Producer1(3,store);
Consumer1 consumer1=new Consumer1(4,store);
Producer1 producer2=new Producer1(5,store);
Consumer1 consumer2=new Consumer1(6,store);
Producer1 producer3=new Producer1(7,store);
Consumer1 consumer3=new Consumer1(8,store);
producer.start();
consumer.start();
producer1.start();
consumer1.start();
producer2.start();
consumer2.start();
producer3.start();
consumer3.start();
}
}
class Storage
{
<span style="color:#FF0000;">private final Lock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();</span>
int realNum;
int maxNum=10;
Storage(int realNum)
{
this.realNum=realNum;
}
void produce(int num) throws InterruptedException
{
lock.lock();
while(realNum==maxNum)
{
System.out.println("仓库已满,生产等待");
<span style="color:#FF0000;">notFull.await();</span>//表示已经满了,notFull等待
}
<span style="color:#FF0000;">if(realNum+num>maxNum)num=maxNum-realNum;</span>//根据仓库大小,设置最大生产量
realNum+=num;
System.out.println("生产:"+num+" realNum:"+realNum);
notEmpty.signalAll();//类似notifyAll()
lock.unlock();
}
void consume(int num) throws InterruptedException
{
lock.lock();
while(realNum<num)
{
System.out.println("库存量不足,消费等待");
notEmpty.await();
}
realNum-=num;
System.out.println("消费:"+num+" realNum:"+realNum);
notFull.signalAll();
lock.unlock();
}
}
class Producer1 extends Thread
{
private Storage store;
private int num;
Producer1(int num,Storage store)
{
this.num=num;
this.store=store;
}
@Override
public void run()
{
// TODO Auto-generated method stub
try {
store.produce(num);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
class Consumer1 extends Thread
{
private Storage store;
private int num;
Consumer1(int num,Storage store)
{
this.num=num;
this.store=store;
}
@Override
public void run()
{
// TODO Auto-generated method stub
try {
store.consume(num);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
3、BlockingQueue阻塞队列方法
如果BlockQueue是空的,从BlockingQueue取东西的操作将会被阻断进入等待状态,直到BlockingQueue进了东西才会被唤醒.同样,如果BlockingQueue是满的,任何试图往里存东西的操作也会被阻断进入等待状态,直到BlockingQueue里有空间才会被唤醒继续操作.
BlockingQueue 实现主要用于生产者-使用者队列。BlockingQueue 实现是线程安全的。所有排队方法都可以使用内部锁定或其他形式的并发控制来自动达到它们的目的。BlockingQueue 可以安全地与多个生产者和多个使用者一起使用。它直接代替了缓冲区的功能。
public class ProducerConsumerProblem
{
public static void main(String[] args)
{
BlockingQueue<Object> q = new LinkedBlockingQueue<Object>(10);
Producer1 p = new Producer1(q);
Consumer1 c1 = new Consumer1(q);
Consumer1 c2 = new Consumer1(q);
new Thread(p).start();
new Thread(c1).start();
new Thread(c2).start();
}
}
class Producer1 implements Runnable
{
private final BlockingQueue<Object> queue;
Producer1(BlockingQueue<Object> q)
{
queue = q;
}
public void run()
{
while(true)
{
Object o=new Object();
try {
queue.put(o);
System.out.println("生产:"+1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
class Consumer1 implements Runnable
{
private final BlockingQueue<Object> queue;
Consumer1(BlockingQueue<Object> q)
{
queue = q;
}
public void run()
{
while(true)
{
Object o=new Object();
try {
queue.take();
System.out.println("消费:"+1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
4、PipedInputStream/PipedOutputStream方法
通常用于缓冲区数据的读写操作
传送输入流应该连接到传送输出流;传送输入流会提供要写入传送输出流的所有数据字节。通常,数据由某个线程从 PipedInputStream
对象读取,并由其他线程将其写入到相应的 PipedOutputStream
。不建议对这两个对象尝试使用单个线程,因为这样可能会死锁该线程。传送输入流包含一个缓冲区,可在缓冲区限定的范围内将读操作和写操作分离开。
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
public class ProducerConsumerProblem
{
public static void main(String[] args) throws IOException
{
PipedOutputStream pout = new PipedOutputStream();
PipedInputStream pin = new PipedInputStream(pout);
DataProducer dw = new DataProducer(pout, 20);
DataConsumer dr = new DataConsumer(pin);
dw.start();
dr.start();
}
}
class DataProducer extends Thread
{
private DataOutputStream dos;
private int num;
DataProducer(OutputStream out, int num)
{
dos = new DataOutputStream(out);
this.num = num;
}
@Override
public void run()
{
for (int i = 0; i < num; i++) {
try {
dos.writeInt(i);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
class DataConsumer extends Thread
{
private DataInputStream dis;
DataConsumer(InputStream in)
{
this.dis = new DataInputStream(in);
}
@Override
public void run()
{
try {
while (true) {
System.out.println(dis.readInt());
}
}catch (IOException ex) {
if (ex.getMessage().equals("Pipe broken")
|| ex.getMessage().equals("Write end dead")) {
// normal termination
return;
}
ex.printStackTrace();
}
}
}