在java多线程编程中,生产者和消费者问题,一直都是一个非常经典的问题,也是充分利用线程同步,对象锁等概念的具体实现,通常情况下也有很多地方能够使用到这种编程模型,下面通过几个例子来简单说明下,解决生产者消费者问题的方法
一.使用同步锁,以及wait和notify来解决生产者消费者问题,首先我们来看看下面的代码:
</pre><span >对于生产者的代码实现为:</span><p></p><p><span ></span></p><pre name="code" class="java">public class Producers extends Thread {
// 每次生产的产品数量
private int num = 3;
private ProductStore myProductStore;
public Producers(ProductStore myProductStore) {
this.myProductStore = myProductStore;
}
@Override
public void run() {
super.run();
if (myProductStore != null) {
myProductStore.produce(num);
}
}
public ProductStore getMyProductStore() {
return myProductStore;
}
public void setMyProductStore(ProductStore myProductStore) {
this.myProductStore = myProductStore;
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
}
对于消费者的代码实现为:
public class Consumers extends Thread {
// 每次生产的产品数量
private int num = 5;
private ProductStore myProductStore;
public Consumers(ProductStore myProductStore) {
this.myProductStore = myProductStore;
}
@Override
public void run() {
super.run();
if (myProductStore != null) {
myProductStore.consumption(num);
}
}
public ProductStore getMyProductStore() {
return myProductStore;
}
public void setMyProductStore(ProductStore myProductStore) {
this.myProductStore = myProductStore;
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
}
产品库类实现:
public class ProductStore {
// 产品库的最大容量
private final int MAX_SIZE = 10;
// 产品库
private final ArrayList<Object> storeList = new ArrayList<Object>();
/**
*
* @Title: produce
* @Description: 生成num个产品
* @date 2014-8-18
* @version 1.0
*/
public void produce(int num) {
synchronized (storeList) {
while (storeList.size() + num > MAX_SIZE) {
System.out.println("需要创建的产品已经超出了,仓库的容量");
try {
storeList.notifyAll();
// 生成线程阻塞
storeList.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (int i = 0; i < num; i++) {
System.out.println("create----------->");
storeList.add(new Object());
}
storeList.notifyAll();
}
}
/**
*
* @Title: consumption
* @Description: 消费num件商品
* @date 2014-8-18
* @version 1.0
*/
public void consumption(int num) {
synchronized (storeList) {
while (storeList.size() < num) {
try {
storeList.notifyAll();
// 生成线程阻塞
storeList.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 否则消费num个产品
for (int i = 0; i < num; i++) {
System.out.println("remove----------->");
storeList.remove(0);
}
storeList.notifyAll();
}
}
}
</pre></p>你可以简单的做一些测试,就是同时建立几个生产者,几个消费者线程,同时运行然后观察效果,这里我们要分析下产品库类的相关代码,其中MAX_SIZE为产品库最大的容量,这个容量的限制是当产品库的容量大于了MAX_SIZE时先唤醒,处于等待状态的消费者线程,然后让调用了产品库的wait 方法让生产者线程停止执行,并且释放同步锁,其他线程就可以获得该同步锁,并且开始执行相关的操作,当产品库位<p><span style="font-family:FangSong_GB2312;font-size:14px;"> 1.你可能会疑问为什么这里使用while而不是使用if来判断呢,实际上因为我们目前的操作是在多线程的环境下进行的,也就是说当其他线程调用了notifyAll后,我们的执行是从wait的下一句代码开始执行的,如果这里不用while而是用if,那么他没有进行条件的判断,直接跳出判断条件往下走,这时候,可能其他的线程并没有完成生产或者消费,可能相关的条件并不满足,这时候我们往下执行就会产生一些问题。</span></p><p><span style="font-family:FangSong_GB2312;font-size:14px;"> 2.<span style="margin: 0px; padding: 0px; line-height: 23px; ">wait</span><span style="margin: 0px; padding: 0px; line-height: 23px; ">被调用的时候必须在拥有锁(即</span><span style="margin: 0px; padding: 0px; line-height: 23px; ">synchronized</span><span style="margin: 0px; padding: 0px; line-height: 23px; ">修饰的)的代码块中,也就是说wait必须在同步代码块中进行调用。</span></span></p><p><span style="font-family:FangSong_GB2312;font-size:14px;"><span style="margin: 0px; padding: 0px; line-height: 23px; "> 3.<span style="color: rgb(51, 51, 51); line-height: 23px; ">若</span><span style="margin: 0px; padding: 0px; color: rgb(51, 51, 51); line-height: 23px; ">wait</span><span style="margin: 0px; padding: 0px; color: rgb(51, 51, 51); line-height: 23px; ">方法参数中带时间,则除了</span><span style="margin: 0px; padding: 0px; color: rgb(51, 51, 51); line-height: 23px; ">notify</span><span style="margin: 0px; padding: 0px; color: rgb(51, 51, 51); line-height: 23px; ">和</span><span style="margin: 0px; padding: 0px; color: rgb(51, 51, 51); line-height: 23px; ">notifyAll</span><span style="margin: 0px; padding: 0px; color: rgb(51, 51, 51); line-height: 23px; ">被调用能激活处于</span><span style="margin: 0px; padding: 0px; color: rgb(51, 51, 51); line-height: 23px; ">wait</span><span style="margin: 0px; padding: 0px; color: rgb(51, 51, 51); line-height: 23px; ">状态(等待状态)的线程进入锁竞争外,在其他线程中</span><span style="margin: 0px; padding: 0px; color: rgb(51, 51, 51); line-height: 23px; ">interrupt</span><span style="margin: 0px; padding: 0px; color: rgb(51, 51, 51); line-height: 23px; ">它或者参数时间到了之后,该线程也将被激活到竞争状态。</span></span></span></p><p><span style="font-family:FangSong_GB2312;font-size:14px;color:#333333;"><span style="line-height: 23px;"> 4.<span style="margin: 0px; padding: 0px; color: rgb(51, 51, 51); line-height: 23px; ">wait</span><span style="margin: 0px; padding: 0px; color: rgb(51, 51, 51); line-height: 23px; ">方法被调用的线程必须获得之前执行到</span><span style="margin: 0px; padding: 0px; color: rgb(51, 51, 51); line-height: 23px; ">wait</span><span style="margin: 0px; padding: 0px; color: rgb(51, 51, 51); line-height: 23px; ">时释放掉的锁重新获得才能够恢复执行。</span></span></span></p><p><span style="font-family:FangSong_GB2312;font-size:14px;color:#333333;"><span style="line-height: 23px;"><span style="margin: 0px; padding: 0px; color: rgb(51, 51, 51); line-height: 23px; "> 5.notify 和notifyAll 的区别是,notify是唤醒某一个在该对象锁上处于等待状态的线程,注意这里我们并不知道,具体是那个线程,如果有多个线程,当其中一个线程被唤醒并完成线程的执行后,其他的线程如果没有在任何时机,有调用notify或者notifyAll都将处于等待状态,不会因为现在资源已经可用了而自动唤醒,除非有其他地方调用了,notify或者notifyAll,而notifyAll则是唤醒所有在改对象锁基础上,处于等待状态的线程,他们可用平等的竞争锁资源,这里可以根据实际的情况决定使用哪种锁。</span></span></span></p><p><span style="font-family:FangSong_GB2312;font-size:14px;color:#333333;"><span style="line-height: 23px;"> 6.关于<span style="font-family: FangSong_GB2312;font-size:14px; line-height: 23px; ">synchronized,在JDK1.5后我们也可以使用lock来实现相同的效果。</span></span></span></p><p><span style="font-family:FangSong_GB2312;font-size:14px;"><strong>二.使用<span style="line-height: 26px; text-indent: 28px; ">阻塞队列来实现:</span></strong></span></p><p style="text-indent: 28px;"><span style="font-family:FangSong_GB2312;font-size:14px;"><span style="line-height: 26px;">在JDK1.5后,java提出了阻塞队列,使用阻塞队列我们可以很简单的解决生产者消费者问题</span></span></p><p style="text-indent: 28px;"><span style="font-family:FangSong_GB2312;font-size:14px;"><span style="line-height: 26px;"></span></span><pre name="code" class="java">public class ProductStore {
// 产品库的最大容量
private final int MAX_SIZE = 10;
// 产品库
private final LinkedBlockingQueue<Object> storeList = new LinkedBlockingQueue<Object>();
/**
*
* @Title: produce
* @Description: 生成num个产品
* @date 2014-8-18
* @version 1.0
*/
public void produce(int num) {
for (int i = 0; i < num; i++) {
try {
// 放入产品,自动阻塞
storeList.put(new Object());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
*
* @Title: consumption
* @Description: 消费num件商品
* @date 2014-8-18
* @version 1.0
*/
public void consumption(int num) {
for (int i = 0; i < num; i++) {
try {
// 放入产品,自动阻塞
storeList.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
对阻塞队列继续以下说明
当缓冲区已满,生产者在
put()
操作时,
put()
内部调用了
await()
方法,放弃了线程的执行,线程的执行会在put函数调用的地方阻塞下来,直到有其他的线程调用了take方法,take()内部调用了signal()方法,通知生产者线程可以执行。这样我们就解决了生产者消费者问题。
三.使用PipedInputStream/PipedOutputStream来实现:
在JDK1.5后,java提出了管道流,就像其他的输入输入输出流一样,向输入流里面输入数据,从输出流里面获得数据,这样就实现类似一个生产者消费者的模式,但是java的管道流还不允许传输对象。
在java中,PipedOutputStream和PipedInputStream分别是管道输出流和管道输入流。它们的作用是让多线程可以通过管道进行线程间的通讯。在使用管道通信时,必须将PipedOutputStream和PipedInputStream配套使用。使用管道通信时,大致的流程是:我们在线程A中向PipedOutputStream中写入数据,这些数据会自动的发送到与PipedOutputStream对应的PipedInputStream中,进而存储在PipedInputStream的缓冲中;此时,线程B通过读取PipedInputStream中的数据。就可以实现,线程A和线程B的通信
四.使用await()/signal()来实现:
在JDK5.0之后,Java提供了更加健壮的线程处理机制,包括同步、锁定、线程池等,它们可以实现更细粒度的线程控制。await()和signal()就是其中用来做同步的两种方法,它们的功能基本上和wait() / nofity()相同,完全可以取代它们,但是它们和新引入的锁定机制Lock直接挂钩,具有更大的灵活性。通过在Lock对象上调用newCondition()方法,将条件变量和一个锁对象进行绑定,进而控制并发程序访问竞争资源的安全。
</pre><pre name="code" class="java">public class ProductStore {
// 产品库的最大容量
private final int MAX_SIZE = 10;
// 产品库
private final ArrayList<Object> storeList = new ArrayList<Object>();
// 锁
private final Lock lock = new ReentrantLock();
// 仓库满的条件变量
private final Condition full = lock.newCondition();
// 仓库空的条件变量
private final Condition empty = lock.newCondition();
/**
*
* @Title: produce
* @Description: 生成num个产品
* @date 2014-8-18
* @version 1.0
*/
public void produce(int num) {
// 获得锁
lock.lock();
while (storeList.size() + num > MAX_SIZE) {
System.out.println("需要创建的产品已经超出了,仓库的容量");
try {
// 生成线程阻塞
full.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (int i = 0; i < num; i++) {
System.out.println("create----------->");
storeList.add(new Object());
}
// 唤醒其他所有线程
empty.signalAll();
// 释放锁
lock.unlock();
}
/**
*
* @Title: consumption
* @Description: 消费num件商品
* @date 2014-8-18
* @version 1.0
*/
public void consumption(int num) {
// 获得锁
lock.lock();
while (storeList.size() < num) {
try {
// 生成线程阻塞
empty.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 否则消费num个产品
for (int i = 0; i < num; i++) {
System.out.println("remove----------->");
storeList.remove(0);
}
// 唤醒其他所有线程
full.signalAll();
// 释放锁
lock.unlock();
}
}