生产者消费者模式
定义
生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的。
优点
- 解耦–生产者。消费者之间不直接通信,降低了耦合度。
- 支持并发
- 支持忙闲不均
实例
肯德基与麦当劳的订餐机制对比: 在肯德基,顾客点单之后,点单员会把所点的食物完成封装之后拿来你面前,然后让你结账,有时候有些耗时操作没完成就会留下一个餐台号稍后送来。而在麦当劳的点餐模型大致是,你点完快餐之后要求你立即付款,付完款之后下一位点餐,而取餐的是在旁边等待,另一个服务员专责负责配餐。
在并发模型中,肯德基比较倾向于一个线程把所有的服务都做完,而麦当劳倾向于服务解耦,让他们更专注于自己的业务。而肯德基的模型与BIO服务器的模型设计类似,麦当劳的模型则与生产者消费者模型十分相似。
实现代码
/**
* @Author: zheng
* @Description: 容量数据类型
* @Date: 2020/8/25
* @Version: 1.0
*/
public class Pcdata {
private final int intData;
public Pcdata(int intData) {
this.intData = intData;
}
public Pcdata(String intData) {
this.intData = Integer.parseInt(intData);
}
public int getIntData() {
return intData;
}
@Override
public String toString() {
return "PCData{" +
"intData=" + intData +
'}';
}
}
/**
* @Author: zheng
* @Description: 生产者线程
* @Date: 2020/8/25
* @Version: 1.0
*/
public class Producer implements Runnable {
private volatile boolean isRunning = true;
/**
* 内存缓冲区
*/
private final BlockingQueue<Pcdata> queue;
/**
* 总数 原子操作
*/
private static final AtomicInteger count = new AtomicInteger();
/**
* 静态常量,睡眠时间(ms)
*/
private static final int SLEEPTIME = 1000;
public Producer(BlockingQueue<Pcdata> queue) {
this.queue = queue;
}
@Override
public void run() {
Pcdata data = null;
Random random = new Random();
System.out.println("start producting id:" + Thread.currentThread().getId());
try {
while (isRunning) {
// 随机休眠时间
Thread.sleep(random.nextInt(SLEEPTIME));
data = new Pcdata(count.incrementAndGet());
System.out.println(data + "加入队列");
if (!queue.offer(data, 2, TimeUnit.SECONDS)) {
System.err.println("加入队列失败");
}
}
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
}
}
public void stop() {
isRunning = false;
}
}
/**
* @Author: zheng
* @Description: 消费者线程
* @Date: 2020/8/25
* @Version: 1.0
*/
public class Consumer implements Runnable{
/**
* 缓冲队列
*/
private final BlockingQueue<Pcdata> queue;
/**
* 静态常量,睡眠时间(ms)
*/
private static final int SLEEPTIME = 1000;
public Consumer(BlockingQueue<Pcdata> queue) {
this.queue = queue;
}
@Override
public void run() {
System.out.println("start Consumer id :" + Thread.currentThread().getId());
Random random = new Random();
try {
while (true) {
Pcdata pcdata = queue.take();
if (pcdata!=null) {
int re = (int) Math.pow(pcdata.getIntData(), 2);
System.out.printf("%d*%d=%d\n", pcdata.getIntData(), pcdata.getIntData(), re);
Thread.sleep(random.nextInt(SLEEPTIME));
}
}
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
}
}
}
/**
* @Author: zheng
* @Description: 主函数
* @Date: 2020/8/25
* @Version: 1.0
*/
public class Main {
public static void main(String[] args) throws InterruptedException {
// 阻塞队列(消息队列)
BlockingQueue<Pcdata> queue = new LinkedBlockingDeque<>(10);
Producer p1 = new Producer(queue);
Producer p2 = new Producer(queue);
Producer p3 = new Producer(queue);
Consumer c1 = new Consumer(queue);
Consumer c2 = new Consumer(queue);
Consumer c3 = new Consumer(queue);
ExecutorService pool = Executors.newCachedThreadPool();
pool.execute(p1);
pool.execute(p2);
pool.execute(p3);
pool.execute(c1);
pool.execute(c2);
pool.execute(c3);
Thread.sleep(10*1000);
p1.stop();
p2.stop();
p3.stop();
Thread.sleep(3000);
pool.shutdown();
}
}
注意:BlockingQueue是阻塞队列,一次只能被一个线程读取。如果用其他数据结构代替必须用synchronized([数据结构的实例对象])代替。最后说一下,欢迎大家留言。