【Java并发编程】生产者消费者模式-上篇

在实际的软件开发过程中,经常会碰到如下场景:某个模块负责产生数据,这些数据由另一个模块来负责处理(此处的模块是广义的,可以是类、函数、线程、进程等)。产生数据的模块,就形象地称为生产者;而处理数据的模块,就称为消费者。

单单抽象出生产者和消费者,还够不上是生产者/消费者模式。该模式还需要有一个缓冲区(也称之为仓库)处于生产者和消费者之间,作为一个中介。生产者把数据放入缓冲区,而消费者从缓冲区取出数据。示意图如下:

生产者消费者问题是研究多线程程序时绕不开的经典问题之一,它描述是有一块缓冲区作为仓库,生产者可以将产品放入仓库,消费者则可以从仓库中取走产品

解决生产者/消费者问题的方法包括以下几种:

1wait() / notify()方法

2await() / signal()方法

3BlockingQueue阻塞队列方法

本篇先介绍(1)(2)两种方法,第三种方法在下篇文章中介绍。

现在给出(1)(2)两种方法解决生产者/消费者问题的通用模板:

 * 1.锁{
 * 2.  while 不满足条件
 *         释放锁等待  条件改变
 *     end while
 * 3.  生产或者消费
 * 4.  唤醒其他生产者或消费者线程
 * 5.  释放锁
 * }

扫描二维码关注公众号,回复: 5119719 查看本文章

(1)wait() / notify()方法

wait() / nofity()方法是基类Object的两个方法,也就意味着所有Java类都会拥有这两个方法,这样,我们就可以为任何对象实现同步机制。

wait()方法:当缓冲区已满/空时,生产者/消费者线程停止自己的执行,放弃锁,使自己处于等等状态,让其他线程执行。

notify()方法:当生产者/消费者向缓冲区放入/取出一个产品时,向其他等待的线程发出可执行的通知,同时放弃锁,使自己处于等待状态。

代码如下:

public class Storage {
	private int count=0;//仓库中的资源数量
	private int maxCount=8;//仓库能存储的最大资源数量
	public synchronized void produce(String name) throws InterruptedException{1.加锁
		while(count>=maxCount){//2.条件不满足
			this.wait();//释放锁,等待条件改变
		}
		count++;//3.生产
		System.out.println(name+"生产一个资源,仓库储量为"+count);
		notifyAll();//4.唤醒其他生产者或消费者线程	
	}//5.释放锁
	public synchronized void consume(String name) throws InterruptedException{1.加锁
		while(count<=0){//2.条件不满足
			this.wait();//释放锁,等待条件改变
		}
		count--;//3.消费
		System.out.println(name+"消费一个资源,仓库储量为"+count);
		notifyAll();//4.唤醒其他生产者或消费者线程	
	}//5.释放锁
	public static void main(String[] args) {
		Storage s=new Storage();
		Thread producer1=new Thread(new Producer(s,"生产者1号"));
		Thread producer2=new Thread(new Producer(s,"生产者2号"));
		
		Thread consumer1=new Thread(new Consumer(s,"消费者1号"));
		Thread consumer2=new Thread(new Consumer(s,"消费者2号"));
		
		producer1.start();
		producer2.start();
		consumer1.start();
		consumer2.start();
	}
}
class Producer implements Runnable{
	private Storage s;
	private String name="";//线程名
	public Producer(Storage s,String name){
		this.s=s;
		this.name=name;
	}
	@Override
	public void run() {
		while(true){
			try {
				s.produce(name);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}	
	}
	
}

class Consumer implements Runnable{
	private Storage s;
	private String name="";//线程名
	public Consumer(Storage s,String name){
		this.s=s;
		this.name=name;
	}
	@Override
	public void run() {
		while(true){
			try {
				s.consume(name);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}	
	}
	
}

 (2)await() / signal()方法

    在JDK5.0之后,Java提供了更加健壮的线程处理机制,包括同步、锁定、线程池等,它们可以实现更细粒度的线程控制。await()和signal()就是其中用来做同步的两种方法,它们的功能基本上和wait() / nofity()相同,完全可以取代它们,但是它们和新引入的锁定机制Lock直接挂钩,具有更大的灵活性。通过在Lock对象上调用newCondition()方法,将条件变量和一个锁对象进行绑定,进而控制并发程序访问竞争资源的安全。下面来看代码:

public class Storage {
	private int count=0;//仓库中的资源数量
	private int maxCount=8;//仓库能存储的最大资源数量
	Lock lock=new ReentrantLock();
	Condition full=lock.newCondition();//生产者等待队列
	Condition empty=lock.newCondition();//消费者等待队列
	
	public  void produce(String name) throws InterruptedException{
		lock.lock();//1.加锁
		
		while(count>=maxCount){//2.条件不满足
			full.await();;//释放锁,等待条件改变
		}
		count++;//3.生产
		System.out.println(name+"生产一个资源,仓库储量为"+count);
		full.signalAll();//4.唤醒其他生产者或消费者线程	
		empty.signalAll();
		
		lock.unlock();//5.释放锁
	}
	public  void consume(String name) throws InterruptedException{
		lock.lock();//1.加锁
		
		while(count<=0){//2.条件不满足
			empty.await();;//释放锁,等待条件改变
		}
		count--;//3.消费
		System.out.println(name+"消费一个资源,仓库储量为"+count);
		
		full.signalAll();//4.唤醒其他生产者或消费者线程	
		empty.signalAll();	
		
		lock.unlock();//5.释放锁
	}//释放锁
	public static void main(String[] args) {
		Storage s=new Storage();
		Thread producer1=new Thread(new Producer(s,"生产者1号"));
		Thread producer2=new Thread(new Producer(s,"生产者2号"));
		
		Thread consumer1=new Thread(new Consumer(s,"消费者1号"));
		Thread consumer2=new Thread(new Consumer(s,"消费者2号"));
		
		producer1.start();
		producer2.start();
		consumer1.start();
		consumer2.start();
	}
}
class Producer implements Runnable{
	private Storage s;
	private String name="";//线程名
	public Producer(Storage s,String name){
		this.s=s;
		this.name=name;
	}
	@Override
	public void run() {
		while(true){
			try {
				s.produce(name);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}	
	}
	
}

class Consumer implements Runnable{
	private Storage s;
	private String name="";//线程名
	public Consumer(Storage s,String name){
		this.s=s;
		this.name=name;
	}
	@Override
	public void run() {
		while(true){
			try {
				s.consume(name);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}	
	}
	
}

上面给出了两种方法解决消费者/生产者模式的代码,下面给出几个最关键的几点:

(1)生产者和消费者使用同一个仓库作为中介。

(2)生产者和消费者使用同一把锁。

(3)生产者只在仓库未满时进行生产,仓库满时生产者进程被阻塞。

(4)消费者只在仓库非空时进行消费,仓库为空时消费者进程被阻塞。

到此为止使用锁机制实现消费者/生产者模式就介绍完了,下一篇将介绍使用阻塞队列实现消费者/生产者模式。

猜你喜欢

转载自blog.csdn.net/fxkcsdn/article/details/86540291