生产者消费者问题(Producer-Consumer Problem)的JAVA实现

一、生产者消费者问题描述

生产者消费者问题(英语:Producer-consumer problem),也称有限缓冲问题(英语:Bounded-buffer problem),是一个多线程同步问题的经典案例。该问题描述了两个共享固定大小缓冲区的线程——即所谓的“生产者”和“消费者”——在实际运行时会发生的问题。

生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。

二、生产者消费者问题的解决办法

要解决该问题,就必须让生产者在缓冲区满时休眠,等到下次消费者消耗缓冲区中的数据的时候,生产者才能被唤醒,开始往缓冲区添加数据。同样,也可以让消费者在缓冲区空时进入休眠,等到生产者往缓冲区添加数据之后,再唤醒消费者。通常采用进程间通信的方法解决该问题,如果解决方法不够完善,则容易出现死锁的情况。出现死锁时,两个线程都会陷入休眠,等待对方唤醒自己。该问题也能被推广到多个生产者和消费者的情形。

三、生产者消费者问题的JAVA实现

为了便于观察输出结果,用5个生产者和5个消费者,1个缓冲区;多生产者、多消费者和多缓冲区本质一样,模仿即可。

缓冲区类代码:

package com.xieyincai.thread;

public class ValueBuffer {

	public static String value = "";
	private static String lock;
	
	public ValueBuffer(String lock) {
		ValueBuffer.lock = lock;
	}
	
	public void setValue() throws InterruptedException {
		synchronized(lock) {
			while(!ValueBuffer.value.equals("")) {
				lock.wait();
			}
			
			String str = System.currentTimeMillis() + "" + System.nanoTime();
			ValueBuffer.value = str;
			System.out.println(Thread.currentThread().getName() + "生产:\t" + ValueBuffer.value);
			
			lock.notifyAll();
		}
	}
	
	public void getValue() throws InterruptedException {
		synchronized(lock) {
			while(ValueBuffer.value.equals("")) {
				lock.wait();
			}
			
			System.out.println(Thread.currentThread().getName() + "消费:\t" + ValueBuffer.value);
			ValueBuffer.value = "";
			
			lock.notifyAll();
		}
	}
}

生产者类代码:

package com.xieyincai.thread;

public class Producer extends Thread {

	private ValueBuffer vbuf;
	public Producer(ValueBuffer vbuf) {
		this.vbuf = vbuf;
	}
	
	public void run() {
		while(true) {
			try {
				vbuf.setValue();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}

消费者类代码:

package com.xieyincai.thread;

public class Consumer extends Thread {

	private ValueBuffer vbuf;
	public Consumer(ValueBuffer vbuf) {
		this.vbuf = vbuf;
	}
	
	public void run() {
		while(true) {
			try {
				vbuf.getValue();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}

测试类代码:

package com.xieyincai.thread;

public class ProducerConsumer {

	public static void main(String[] args) {
		// TODO Auto-generated method stub

		String lock = "LOCK";
		ValueBuffer vbuf = new ValueBuffer(lock);
		
		for(int i=0; i<5; i++) {
			Producer p = new Producer(vbuf);
			p.setName("生产者" + i);
			
			Consumer c = new Consumer(vbuf);
			c.setName("消费者" + i);
			
			p.start();
			c.start();
		}
	}

}

输入结果截图:

生产者1生产:	14960312391682681134961666432
消费者1消费:	14960312391682681134961666432
生产者0生产:	14960312391682681134961739683
消费者2消费:	14960312391682681134961739683
生产者3生产:	14960312391682681134961781217
消费者1消费:	14960312391682681134961781217
生产者4生产:	14960312391682681134961829170
消费者2消费:	14960312391682681134961829170
生产者0生产:	14960312391682681134961845783
消费者2消费:	14960312391682681134961845783
生产者4生产:	14960312391682681134961936780
消费者2消费:	14960312391682681134961936780

JAVA代码补充说明

lock定义为String类型,它是Object的子类(public final class String extends Object implements Serializable, Comparable, CharSequence)。Object类有4个方法对多线程编程很重要,有必要说明一下:

wait():导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法。
wait(long timeout):导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量。
notify() :唤醒在此对象监视器上等待的单个线程。
notifyAll():唤醒在此对象监视器上等待的所有线程。

注:实现中用notifyAll(),而不是notify()。因为notify()只唤醒单个线程(可以是生产者,或者是消费者)。如果生产者唤醒生产者,或消费者唤醒消费者,都可能出现死锁情况。

Leave a Reply

Your email address will not be published. Required fields are marked *