什么是生产者-消费者模式
生产者消费者模式是通过一个缓冲区或者容器来解决生产者和消费者之间强耦合的问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力
这个阻塞队列就是用来给生产者和消费者解耦的。
如果缓冲区已经满了,则生产者线程阻塞;
如果缓冲区为空,那么消费者线程阻塞。
为什么要使用生产者消费者模式
在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者,所以为了达到生产者和消费者生产数据和消费数据之间的平衡,那么就需要一个缓冲区或容器用来存储生产者生产的数据,所以就引入了生产者-消费者模式
生产者-消费者模式的特点
保证生产者不会在缓冲区满的时候继续向缓冲区放入数据,而消费者也不会在缓冲区空的时候,消耗数据
当缓冲区满的时候,生产者会进入休眠状态,当下次消费者开始消耗缓冲区的数据时,生产者才会被唤醒,开始往缓冲区中添加数据;当缓冲区空的时候,消费者也会进入休眠状态,直到生产者往缓冲区中添加数据时才会被唤醒
生产者-消费者模式的优点
解耦:将生产者类和消费者类进行解耦,消除代码之间的依赖性,简化工作负载的管理
调整并发数:由于生产者和消费者的处理速度是不一样的,可以调整并发数,给予慢的一方多的并发数,来提高任务的处理速度
异步:对于生产者和消费者来说能够各司其职,生产者只需要关心缓冲区是否还有数据,不需要等待消费者处理完;同样的对于消费者来说,也只需要关注缓冲区的内容,不需要关注生产者,通过异步的方式支持高并发,将一个耗时的流程拆成生产和消费两个阶段,这样生产者因为执行put()的时间比较短,而支持高并发
支持分布式:生产者和消费者通过队列进行通讯,所以不需要运行在同一台机器上,在分布式环境中可以通过RabbitMQ作为队列,而消费者只需要轮询队列中是否有数据。
销峰(缓冲能力):如果生产者制造数据的速度时快时慢,缓冲区的好处就体现出来了。当数据制造快的时候,消费者来不及处理,未处理的数据可以暂时存在缓冲区中。等生产者的制造速度慢下来,消费者再慢慢处理掉,由此可以平衡生产和消费的速度,提高系统的稳定性
生产者-消费者模式的JAVA实现
先定义一个食物类
/**
* KFC的蛋挞
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Food {
private String name;
}
再定义一个KFC接口和实现类,以队列LinkedBlockingQueue作为中间的缓冲区,最大值MAX_SIZE限制缓存区的大小,
public interface IKFC {
public void produce(String name) throws InterruptedException;
public void consume(String name) throws InterruptedException;
}
public class KFCImpl implements IKFC {
//缓冲
private Queue<Food> queue = new LinkedBlockingQueue<>();
//缓存大小
private final int MAX_SIZE = 10;
@Override
public synchronized void produce(String name) throws InterruptedException {
if (queue.size() >= MAX_SIZE) {
System.out.println("[KFC" + name + "] 蛋挞制作达到上限,停止生成......");
wait();
} else {
Food food = new Food("上校鸡块");
queue.add(food);
System.out.println("[生产者" + name + "] 生成一个:" + food.getName() + ",KFC有蛋挞:" + queue.size() + "个");
//唤醒等待的消费线程
notifyAll();
}
}
@Override
public synchronized void consume(String name) throws InterruptedException {
if (queue.isEmpty()) {
System.out.println("[消费者" + name + "] 蛋挞已空,请等待......");
wait();
} else {
Food food = queue.poll();
System.out.println("[消费者" + name + "] 消费一个:" + food.getName() + ",蛋挞有:" + queue.size() + "个");
//唤醒等待的生产线程
notifyAll();
}
}
}
定义生产者线程和消费者线程
public class ProduceThread extends Thread{
private IKFC kfc;
public ProduceThread(String name,IKFC kfc) {
super(name);
this.kfc = kfc;
}
@Override
public void run() {
while(true){
try {
kfc.produce(getName());
sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class ProduceThread extends Thread {
private IKFC kfc;
public ProduceThread(String name, IKFC kfc) {
super(name);
this.kfc = kfc;
}
@Override
public void run() {
while (true) {
try {
kfc.produce(getName());
sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class ConsumerThread extends Thread {
private IKFC kfc;
public ConsumerThread(String name, IKFC kfc) {
this.kfc = kfc;
}
@Override
public void run() {
while (true) {
try {
kfc.consume(getName());
sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
测试
public class Main {
public static void main(String[] args) {
IKFC kfc = new KFCImpl();
Thread p1= new ProduceThread("A",kfc);
Thread p2= new ProduceThread("B",kfc);
Thread p3= new ProduceThread("C",kfc);
Thread c1 = new ConsumerThread("X",kfc);
Thread c2 = new ConsumerThread("Y",kfc);
Thread c3 = new ConsumerThread("T",kfc);
Thread c4 = new ConsumerThread("Z",kfc);
p1.start();
p2.start();
p3.start();
c1.start();
c2.start();
c3.start();
c4.start();
}
}