生产者消费者模式(简易版)

  • synchronize 中的 wait 和 notifyAll 实现

    • synchronize: 当一个线程进入到同步代码块时,会获取到当前的锁,而这时如果其他使用同样的锁的同步代码块也想执行内容,就必须等待当前同步代码块的内容执行完毕,在执行完毕后会自动释放这把锁,而其他的线程才能拿到这把锁并开始执行同步代码块里面的内容
  • 生产者和消费者中间需要有"一条通道"来进行取和产的存放, 因此会用到数据结构的"队列",队列拥有先进先出的特性

  • 队列是一种特殊的线性表,它只允许在表的前端进行删除操作,而在表的后端进行插入操作


生产者模式

  • 代入厨师的生产模式:生产菜品需要时间,利用Thread.sleep方法进行睡眠(假设做菜时间)
  • 利用synchronized是"谁拿到这个锁谁就可以运行它所控制的那段代码"
    private static void Producer() {
        while (true) {
            try {
                Thread.sleep(3000);
                synchronized (queue){
                    String name = Thread.currentThread().getName();//获取线程名字
                    System.out.println(new Date() + " "+ name+" 出餐了!!!");
                    queue.offer(new Object());//增加菜品进队列
                    queue.notifyAll();//不止有一个客户队列
//唤醒queue.wait的线程, 通知队列客户可以取餐
//因为有好几个客户线程,几个客户线程的队列都可能为空然后被wait,所以需要唤醒几个客户线程的队列
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

消费者模式

  • 代入客户的消费模式:客户取走餐并享用,需要Thread.sleep方法进行睡眠(假设吃饭的时间),sleep需要在"取餐代码"后面(可理解为取走队列中的菜品后才可以sleep享用)
  • 利用synchronized是"谁拿到这个锁谁就可以运行它所控制的那段代码
private static void Consumer() {
        while(true) {
            try {
                synchronized (queue){
                    while (queue.isEmpty()) {queue.wait();}
//需要使用while来不断循环判断队列中是否有菜, 只用if只会判断一次,队列中有可能会有很多菜品
                    queue.poll();
                    String name = Thread.currentThread().getName();//获取线程名字
                    System.out.println(new Date() + " "+ name+" 取走了餐 正在享用!!!");
                }
                Thread.sleep(6000);//取走需要享用的等待时间
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

生产者消费者模式代码(无限循环式)

  • 需要自身停止

image

package ConsumerProduce;//注意自身的包名

import java.util.Date;
import java.util.LinkedList;
import java.util.Queue;

public class ConsumerProduce {
    private static Queue<Object> queue = new LinkedList<>();//基于链表创建队列数据结构
    public static void main(String[] args) {
        new Thread(ConsumerProduce::Producer, "chef.1").start();
        new Thread(ConsumerProduce::Producer, "chef.2").start();

        new Thread(ConsumerProduce::Consumer, "client.1").start();
        new Thread(ConsumerProduce::Consumer, "client.2").start();
    }

    private static void Producer() {
        while (true) {
            try {
                Thread.sleep(3000);
                synchronized (queue){
                    String name = Thread.currentThread().getName();//获取线程名字
                    System.out.println(new Date() + " "+ name+" 出餐了!!!");
                    queue.offer(new Object());
                    queue.notifyAll();
                    //唤醒queue.wait的线程, 通知队列客户可以取餐
                    //因为有好几个客户线程,几个客户线程的队列都可能为空然后被wait,所以需要唤醒几个客户线程的队列
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private static void Consumer() {
        while(true) {
            try {
                synchronized (queue){
                    while (queue.isEmpty()) {queue.wait();}
                    //需要使用while来不断循环判断队列中是否有菜, 只用if只会判断一次,队列中有可能会有很多菜品
                    queue.poll();
                    String name = Thread.currentThread().getName();
                    System.out.println(new Date() + " " + name +" 取走了餐 正在享用!!!");
                }
                Thread.sleep(6000);//取走需要享用的等待时间
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}


注意

  1. 需要注意synchronized的使用; 例如上述代码, 谁获取了queue对象的锁谁就可以运行它所控制的那段代码

  2. 使用的是基于链表创建的队列, 理解为链表样式的队列结构;需要对数据结构队列有一定认识;如offer和poll的操作


代码解析

image

1: chef.1在14s出餐了(需要3s后(17s)才可出餐)

2: client.2在14s取走了(需要6s后(20s)才可以再取餐)

3: chef.2在14s出餐了(需要3s后(17s)才可出餐)

4: client.1在在14s取走了(需要6s后(20s)才可以再取餐)

5: chef.1在17s出餐了(需要3s后(20s)才可出餐)

6: chef.2在17s出餐了(需要3s后(20s)才可出餐)

7: client.1在在20s取走了(需要6s后(26s)才可以再取餐)

8: client.2在在20s取走了(需要6s后(26s)才可以再取餐)

9: chef.1在20s出餐了(需要3s后(23s)才可出餐)

10: chef.2在20s出餐了(需要3s后(23s)才可出餐)

11: chef.2在23s出餐了(需要3s后(26s)才可出餐)

12: chef.2在23s出餐了(需要3s后(26s)才可出餐)

  • chef.1和chef.2各连续出餐两次,并没有client取餐; 原因正是client正在享用(6s进食)

  • 最后两行client.1和client.2都是于26s取走菜品