生产者消费者模式是一个十分经典的多线程协作模式 弄懂生产者消费者问题能够让我们对多线程编程的理解更加深刻
存在3个元素 1.生产者(类比厨师) 2.生产者的生产产品(类比美食) 3.消费者(类比吃货)
思路分析: 理想情况: 最开始,生产者先抢到cpu执行权;生产出生产产品并放在2者位置之间(类比餐桌) 然后消费者抢到cpu执行权,消费掉生产产品 如此反复循环
生产者和消费者简单来说就是2个线程轮流执行
而实际上线程是随机执行的
下面介绍2个异常的情况:
消费者等待过程: 最开始,消费者先抢到cpu执行权; 现在2者之间还未有生产产品,消费者只能在此等待 然后生产者抢到cpu执行权,生产出生产产品,放在2者之间并通知消费者 消费者得到通知,消费掉生产产品
消费者步骤: 1.判断2者之间是否有生产产品 2.如果没有就等待 生产者步骤: 1.生产出生产产品 2.把生产产品放在2者之间 3.通知等待的消费者进行消费
生产者等待过程: 最开始,生产者先抢到cpu执行权;生产出生产产品并放在2者位置之间 然后还是生产者抢到cpu执行权;由于生产产品还未被消费,自然不再进行生产,从而进行等待 然后消费者抢到cpu执行权,消费掉生产产品
生产者步骤: 1.判断2者之间是否有生产产品;如果有就等待,如果没有才生产 2.把生产产品放在2者之间 3.通知等待的消费者进行消费 消费者步骤: 1.判断2者之间是否有生产产品 2.如果没有就等待 3.如果有就消费掉生产产品,2者之间的生产产品就没有了,通知等待的生产者继续生产,生产产品数量减1
代码实现: 此处生产者是厨师,生产产品是汉堡包,消费者是吃货,2者之间是桌子
//主体public class Demo {public static void main(String[] args) {/*消费者步骤:1,判断桌子上是否有汉堡包。2,如果没有就等待。3,如果有就开吃4,吃完之后,桌子上的汉堡包就没有了叫醒等待的生产者继续生产汉堡包的总数量减一*//*生产者步骤:1,判断桌子上是否有汉堡包如果有就等待,如果没有才生产。2,把汉堡包放在桌子上。3,叫醒等待的消费者开吃。*/Foodie f = new Foodie();Cooker c = new Cooker();f.start();c.start();}}//生产者(厨师)public class Cooker extends Thread {// 生产者步骤:// 1,判断桌子上是否有汉堡包// 如果有就等待,如果没有才生产。// 2,把汉堡包放在桌子上。// 3,叫醒等待的消费者开吃。@Overridepublic void run() {while(true){synchronized (Desk.lock){if(Desk.count == 0){break;}else{if(!Desk.flag){//生产System.out.println("厨师正在生产汉堡包");Desk.flag = true;Desk.lock.notifyAll();}else{try {Desk.lock.wait();} catch (InterruptedException e) {e.printStackTrace();}}}}}}}//2者之间(桌子)public class Desk {//定义一个标记//true 就表示桌子上有汉堡包的,此时允许吃货执行//false 就表示桌子上没有汉堡包的,此时允许厨师执行public static boolean flag = false;//汉堡包的总数量public static int count = 10;//锁对象public static final Object lock = new Object();}//消费者(吃货)public class Foodie extends Thread {@Overridepublic void run() {// 1,判断桌子上是否有汉堡包。// 2,如果没有就等待。// 3,如果有就开吃// 4,吃完之后,桌子上的汉堡包就没有了//叫醒等待的生产者继续生产// 汉堡包的总数量减一//套路://1. while(true)死循环//2. synchronized 锁,锁对象要唯一//3. 判断,共享数据是否结束. 结束//4. 判断,共享数据是否结束. 没有结束while(true){synchronized (Desk.lock){if(Desk.count == 0){break;}else{if(Desk.flag){//有System.out.println("吃货在吃汉堡包");Desk.flag = false;Desk.lock.notifyAll();Desk.count--;}else{//没有就等待//使用什么对象当做锁,那么就必须用这个对象去调用等待和唤醒的方法.try {Desk.lock.wait();} catch (InterruptedException e) {e.printStackTrace();}}}}}}}
阻塞队列的基本使用:
阻塞队列(BlockingQueue) 是一个支持两个附加操作的队列
这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空。当队列满时,存储元素的线程会等待队列可用
阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素 阻塞队列继承结构:
BlockingQueue的核心方法: put(anObject):将参数放入队列,如果放不进去会阻塞 take():取出第一个数据,取不到会阻塞
常见的BlockingQueue: ArrayBlockingQueue:底层是数组,有界 LinkedBlockingQueue:底层是链表,无界;但不是真正的无界,最大为int的最大值 示例代码:
import java.util.concurrent.ArrayBlockingQueue;public class Main {public static void main(String[] a) throws Exception{// 创建阻塞队列的对象,容量为 1ArrayBlockingQueue<String> arrayBlockingQueue = new ArrayBlockingQueue<>(1);// 存储元素arrayBlockingQueue.put("汉堡包");// 取元素System.out.println(arrayBlockingQueue.take());System.out.println(arrayBlockingQueue.take()); // 取不到会阻塞System.out.println("程序结束了");//不会进行这段语句,因为上面语句已经导致阻塞了}}
阻塞队列实现等待唤醒机制:
案例简要:
生产者类(Cooker):实现Runnable接口,重写run()方法,设置线程任务
1.构造方法中接收一个阻塞队列对象
2.在run方法中循环向阻塞队列中添加包子
3.打印添加结果
消费者类(Foodie):实现Runnable接口,重写run()方法,设置线程任务
1.构造方法中接收一个阻塞队列对象
2.在run方法中循环获取阻塞队列中的包子
3.打印获取结果
测试类(Demo):里面有main方法,main方法中的代码步骤如下
创建阻塞队列对象
创建生产者线程和消费者线程对象,构造方法中传入阻塞队列对象
分别开启两个线程
代码实现:
//主体public class Demo {public static void main(String[] args) {ArrayBlockingQueue<String> bd = new ArrayBlockingQueue<>(1);Foodie f = new Foodie(bd);Cooker c = new Cooker(bd);f.start();c.start();}//生产者(厨师)public class Cooker extends Thread {private ArrayBlockingQueue<String> bd;public Cooker(ArrayBlockingQueue<String> bd) {this.bd = bd;}// 生产者步骤:// 1,判断桌子上是否有汉堡包// 如果有就等待,如果没有才生产。// 2,把汉堡包放在桌子上。// 3,叫醒等待的消费者开吃。@Overridepublic void run() {while (true) {try {bd.put("汉堡包");System.out.println("厨师放入一个汉堡包");} catch (InterruptedException e) {e.printStackTrace();}}}}//消费者(吃货)public class Foodie extends Thread {private ArrayBlockingQueue<String> bd;public Foodie(ArrayBlockingQueue<String> bd) {this.bd = bd;}@Overridepublic void run() {// 1,判断桌子上是否有汉堡包。// 2,如果没有就等待。// 3,如果有就开吃// 4,吃完之后,桌子上的汉堡包就没有了// 叫醒等待的生产者继续生产// 汉堡包的总数量减一//套路://1. while(true)死循环//2. synchronized 锁,锁对象要唯一//3. 判断,共享数据是否结束. 结束//4. 判断,共享数据是否结束. 没有结束while (true) {try {String take = bd.take();System.out.println("吃货将" + take + "拿出来吃了");} catch (InterruptedException e) {e.printStackTrace();}}}}