生产者和消费者
概述:
生产者消费者问题,实际上主要是包含了两类线程:
- 生产者线程用于生产数据
- 消费者线程用于消费数据
生产者和消费者之间通常会采用一个共享的数据区域,这样就可以将生产者和消费者进行解耦,
两者都不需要互相关注对方的
方法:
Object类的等待和唤醒方法
方法名 | 说明 |
void wait() | 导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法 |
void notify() | 唤醒正在等待对象监视器的单个线程 |
void notifyAll() | 唤醒正在等待对象监视器的所有线程 |
案例需求:
- 桌子类(Desk):定义表示双吉芝士汉堡数量的变量,定义锁对象变量,定义标记桌子上有无双吉芝士汉堡的变量
- 生产者类(Cooker):实现Runnable接口,重写run()方法,设置线程任务
1.判断是否有双吉芝士汉堡,决定当前线程是否执行
2.如果有双吉芝士汉堡,就进入等待状态,如果没有双吉芝士汉堡继续执行,生产双吉芝士汉堡
3.生产双吉芝士汉堡之后,更新桌子上双吉芝士汉堡状态,唤醒消费者消费双吉芝士汉堡 - 消费者类(Foodie):实现Runnable接口,重写run()方法,设置线程任务
1.判断是否有双吉芝士汉堡,决定当前线程是否执行
2.如果没有双吉芝士汉堡,就进入等待状态,如果有双吉芝士汉堡,就消费双吉芝士汉堡
3.消费双吉芝士汉堡后,更新桌子上双吉芝士汉堡状态,唤醒生产者生产双吉芝士汉堡 - 测试类(Demo):里面有main方法,main方法中的代码步骤如下
创建生产者线程和消费者线程对象
分别开启两个线程
/** * @Author:kkoneone11 * @name:Cooker * @Date:2023/8/27 18:55 */ public class Cooker extends Thread{ private Desk desk; public Cooker(Desk desk){ this.desk = desk; } // 生产者步骤: // 1,判断桌子上是否有双吉芝士汉堡 // 如果有就等待,如果没有才生产。 // 2,把双吉芝士汉堡放在桌子上。 // 3,叫醒等待的消费者开吃。 @Override public void run(){ while(true){ synchronized (desk.getLock()){ if(desk.getCount() == 0){ break; }else { if(!desk.isFlag()){ System.out.println("厨师正在制作双吉芝士汉堡"); //生产双层吉士 desk.setFlag(true); //叫醒麦门弟子干饭 desk.getLock().notifyAll(); }else{ try{ desk.getLock().wait(); }catch (Exception e){ e.printStackTrace(); } } } } } } } public class Foodie extends Thread{ private Desk desk; public Foodie(Desk desk){ this.desk = desk; } // 1,判断桌子上是否有双吉芝士汉堡。 // 2,如果没有就等待。 // 3,如果有就开吃 // 4,吃完之后,桌子上的双吉芝士汉堡就没有了 // 叫醒等待的生产者继续生产 // 双吉芝士汉堡的总数量减一 @Override public void run(){ while(true){ synchronized (desk.getLock()){ if(desk.getCount() == 0){ break; }else { if(desk.isFlag()){ //有双层吉士 System.out.println("麦门弟子疯狂炫吧"); desk.setFlag(false); desk.getLock().notifyAll(); desk.setCount(desk.getCount() -1); }else{ //没有双层吉士 等待 //使用什么对象当做锁,那么就必须用这个对象去调用等待和唤醒的方法. try { desk.getLock().wait(); }catch (Exception e){ e.printStackTrace(); } } } } } } } public class Demo { public static void main(String[] args) { Desk desk = new Desk(); Foodie f = new Foodie(desk); Cooker c = new Cooker(desk); f.start(); c.start(); } }
阻塞队列:
阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素
阻塞队列继承结构:
常见BlockingQueue的实现类:
- ArrayBlockingQueue: 底层是数组,有界
- LinkedBlockingQueue: 底层是链表,无界.但不是真正的无界,最大为int的最大值
方法:
实例:
public class Demo { public static void main(String[] args) throws Exception { // 创建阻塞队列的对象,容量为 1 ArrayBlockingQueue<String> arrayBlockingQueue = new ArrayBlockingQueue<>(1); // 存储元素 arrayBlockingQueue.put("双层吉士"); // 取元素 System.out.println(arrayBlockingQueue.take()); System.out.println(arrayBlockingQueue.take()); // 取不到会阻塞 System.out.println("程序结束了"); } }
案例需求优化:
不再需要Desk这个类,改用阻塞队列
public class Cooker extends Thread{ private ArrayBlockingQueue<String> bd; public Cooker(ArrayBlockingQueue<String> bd) { this.bd = bd; } // 生产者步骤: // 1,判断桌子上是否有汉堡包 // 如果有就等待,如果没有才生产。 // 2,把汉堡包放在桌子上。 // 3,叫醒等待的消费者开吃。 @Override public 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; } @Override public void run() { // 1,判断桌子上是否有汉堡包。 // 2,如果没有就等待。 // 3,如果有就开吃 // 4,吃完之后,桌子上的汉堡包就没有了 // 叫醒等待的生产者继续生产 // 汉堡包的总数量减一 while (true) { try { String take = bd.take(); System.out.println("吃货将" + take + "拿出来吃了"); } catch (InterruptedException e) { e.printStackTrace(); } } } } 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(); } }