生产者和消费者问题是线程模型中的经典问题,生产者和消费者在同一时间段内共用一个存储空间,生产者在其中添加产品,消费者从其中取走产品,当存储空间为空时,消费者阻塞,当存储空间满了时,生产者阻塞。
举例:
中间的KFC相当于缓冲区,生产者生产产品放到其中,消费者从其中拿走并消费。
生产者-消费者模式的优点
- 解耦:将生产者类和消费者类进行解耦,消除代码之间的依赖性。
- 复用:通过将生产者类和消费者类独立开来,那么可以对生产者类和消费者类进行独立的复用与扩展。
- 调整并发数:由于生产者和消费者的处理速度是不一样的,可以调整并发数,给予慢的一方多的并发数,来提高任务的处理速度。
- 异步:对于生产者和消费者来说能够各司其职,生产者只需要关心缓冲区是否还有数据,不需要等待消费者处理完;同样的对于消费者来说,也只需要关注缓冲区的内容,不需要关注生产者,通过异步的方式支持高并发,将一个耗时的流程拆成生产和消费两个阶段,这样生产者因为执行put()的时间比较短,而支持高并发。
- 支持分布式:生产者和消费者通过队列进行通讯,所以不需要运行在同一台机器上,在分布式环境中可以通过redis的list作为队列,而消费者只需要轮询队列中是否有数据。同时还能支持集群的伸缩性,当某台机器宕掉的时候,不会导致整个集群宕掉。
生产者-消费者模式的实现
它的底层实现方式:数组,链表。
主要关心两个问题:
1、如何保证容器中的数据状态一致
2、如何保证消费者和生产者之间的同步和协作关系。
案例:
1、定义实体类
/**
* KFC中的产品
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Food {
private String name;
}
基础实现、缓冲区满或空时都调用wait()方法等待,当生产者生产了一个产品时或消费者消费了一个产品之后会唤醒所有线程。
public class KFCImpl implements IKFC {
private Queue<Food> queue = new LinkedBlockingQueue<>();
private final int MAX_SIZE = 10;
@Override
public synchronized void produce() throws InterruptedException {
if (queue.size() >= MAX_SIZE) {
System.out.println("[生产者] KFC生成达到上限,停止生成......");
wait();
} else {
Food food = new Food("上校鸡块");
queue.add(food);
System.out.println("[生产者] 生成一个:" + food.getName() + ",KFC有食物:" + queue.size() + "个");
//唤醒等待的线程来消费
notifyAll();
}
}
@Override
public synchronized void consume() throws InterruptedException {
if (queue.isEmpty()) {
System.out.println("[消费者者] KFC食物已空,消费者停止消费......");
wait();
}
Food food = queue.poll();
System.out.println("[消费者] 消费一个:" + food.getName() + ",KFC有食物:" + queue.size() + "个");
//唤醒等待的线程来消费
notifyAll();
}
}
public class ConsumerThread extends Thread{
private IKFC kfc;
public ConsumerThread(String name, IKFC kfc) {
super(name);
this.kfc = kfc;
}
@Override
public void run() {
while(true){
try {
kfc.consume();
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);
Thread c5 = new ConsumerThread("K",kfc);
p1.start();
p2.start();
p3.start();
c1.start();
c2.start();
c3.start();
c4.start();
c5.start();
}
}
测试结果: