生产者和消费者模式
在软件开发过程中,有产生数据的代码模块,有处理数据的代码模块,在生产者和消费者模式中,产生数据的模块就称之为生产者,处理数据的模块就称之为消费者。还有一个存储区连接着生产者和消费者,生产者生产的数据放到存储区中,而消费者取数据也是从存储区中去取,这个存储区是有最大容量限制的,如果存储区中的数据达到了最大容量,那么就要让生产者暂停生产数据,等有容量了在进行生产。如果存储区中没有数据,那么就要让消费者暂停处理数据,等到有数据了在进行消费。生产者和消费者模式要解决的问题就是生产者和消费者之间处理数据的动态平衡问题。
举个例子,比如现在有个男孩(生产者)、有个女孩(消费者)、有个篮子(存储区)、篮子里装的是苹果(数据)。假设篮子里最多能装30个苹果,那么下面要解决的问题就是男孩往篮子里装苹果和女孩从篮子里拿苹果的一个动态平衡问题。下面通过代码进行实现,生产者和消费者实现主要有两种方式,一种是通过wait和notify,一种是通过阻塞队列BlockingQueue实现。
方式一:wait和notify方式
篮子(存储区)代码:
package cn.znh.procon;
import java.util.ArrayList;
import java.util.List;
/**
* 篮子
*
* @author znh
*
*/
public class Basket {
// 装苹果的篮子
public static List<String> basket = new ArrayList<>();
}
男孩(生产者)代码:
package cn.znh.procon;
/**
* 生产者
*
* @author znh
*
*/
public class Boy extends Thread {
@Override
public void run() {
while (true) {
synchronized (Basket.basket) {
// 如果篮子里的苹果超过了最大容量,就让男孩休眠
if (Basket.basket.size() >= 30) {
try {
Basket.basket.wait();
} catch (Exception e) {
e.printStackTrace();
}
}
// 男孩往篮子里添加苹果
Basket.basket.add("苹果");
System.out.println("男孩向篮子里添加了一个苹果,现在篮子里还有" + Basket.basket.size() + "个苹果");
// 经过上面的add代码后,篮子里肯定有苹果了,就唤醒女孩吃苹果
Basket.basket.notify();
}
// 为了方便查看结果,这里等待一下
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
女孩(消费者)代码:
package cn.znh.procon;
/**
*
* 消费者
*
* @author znh
*
*/
public class Girl extends Thread {
@Override
public void run() {
while (true) {
synchronized (Basket.basket) {
// 如果篮子里没有苹果了,就让女孩休眠
if (Basket.basket.size() <= 0) {
try {
Basket.basket.wait();
} catch (Exception e) {
e.printStackTrace();
}
}
// 女孩消费篮子里的苹果
Basket.basket.remove(0);
System.out.println("女孩从篮子里拿了一个苹果,现在篮子里还有" + Basket.basket.size() + "个苹果");
// 经过上面的remove代码后,篮子里肯定没有满,就唤醒男孩往篮子里添加苹果
Basket.basket.notify();
}
// 为了方便查看结果,这里等待一下
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
执行代码:
public static void main(String[] args) {
//创建线程
Boy boy = new Boy();
Girl girl = new Girl();
//开始执行
boy.start();
girl.start();
}
方式二:阻塞队列方式
篮子(存储区)代码:
// 创建阻塞队列,指定最大容量为30
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(30);
男孩(生产者)代码:
package cn.znh.procon.queue;
import java.util.concurrent.BlockingQueue;
/**
* 生产者
*
* @author znh
*
*/
public class Boy extends Thread {
// 阻塞队列(装苹果的篮子)
private BlockingQueue<String> blockingQueue;
public Boy(BlockingQueue<String> blockingQueue) {
this.blockingQueue = blockingQueue;
}
@Override
public void run() {
while (true) {
// 男孩向篮子里添加苹果
try {
blockingQueue.put("苹果");
System.out.println("男孩向篮子里添加了一个苹果,现在篮子里还有" + blockingQueue.size() + "个苹果");
} catch (InterruptedException e1) {
e1.printStackTrace();
}
// 为了方便查看结果,这里等待一下
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
女孩(消费者)代码:
package cn.znh.procon.queue;
import java.util.concurrent.BlockingQueue;
/**
*
* 消费者
*
* @author znh
*
*/
public class Girl extends Thread {
// 阻塞队列(装苹果的篮子)
private BlockingQueue<String> blockingQueue;
public Girl(BlockingQueue<String> blockingQueue) {
this.blockingQueue = blockingQueue;
}
@Override
public void run() {
while (true) {
// 女孩消费篮子里的苹果
try {
blockingQueue.take();
System.out.println("女孩从篮子里拿了一个苹果,现在篮子里还有" +blockingQueue.size() + "个苹果");
} catch (InterruptedException e) {
e.printStackTrace();
}
// 为了方便查看结果,这里等待一下
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
执行代码:
public static void main(String[] args) {
// 创建阻塞队列,指定最大容量为30
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(30);
// 通过构造方法将阻塞队列传递到生产者和消费者中
Boy boy = new Boy(blockingQueue);
Girl girl = new Girl(blockingQueue);
// 开始执行代码
boy.start();
girl.start();
}
上面是两种方式实现的生产者和消费者模式的代码,从两种方式对比来看,它们所达到的效果是一样的,都保持了数据处理的动态平衡,但是方式二使用起来比方式一更简单一些。