Java生产者消费者模型详细实现

1、生产者-消费者模式概念

1.1重点

生产者如何安全的把数据交给消费者

1.2实际案例

​ 蛋糕师傅:做蛋糕放在桌子上,如果桌子上放满了,师傅要等待,直到桌子上有空位才继续放蛋糕。

​ 吃货:吃货吃桌子上的蛋糕,如果桌子上一个蛋糕也没有,吃货需要等待,直到有一个蛋糕位置。

​ 桌子:放蛋糕,最多放n个蛋糕,取蛋糕和放蛋糕顺序一致

​ 蛋糕:直接使用String

​ 疑问:如何等待?如何唤醒?

​ 等待:wait()

​ 唤醒:notity(),notityAll();

需要创建的类
Table放蛋糕取蛋糕
MakerThread先把蛋糕生产出来,调用Table的放蛋糕方法
Eatertread调用Table的取蛋糕方法
Main测试类
1.3编写代码前我们需要了解
Class ArrayBlockingQueue
  • public class ArrayBlockingQueue
    extends AbstractQueue
    implements BlockingQueue, Serializable
  • 一个有限的blocking queue由数组支持。 这个队列排列元素FIFO(先进先出)。 队列的头部是队列中最长的元素。 队列的尾部是队列中最短时间的元素。 新元素插入队列的尾部,队列检索操作获取队列头部的元素。
  • 这是一个经典的“有界缓冲区”,其中固定大小的数组保存由生产者插入的元素并由消费者提取。 创建后,容量无法更改。 尝试put成满的队列的元件将导致在操作阻挡; 尝试take从空队列的元件将类似地阻塞
  • 此类支持可选的公平策略,用于订购等待的生产者和消费者线程。 默认情况下,此订单不能保证。 然而,以公平设置为true的队列以FIFO顺序授予线程访问权限。 公平性通常会降低吞吐量,但会降低变异性并避免饥饿。
  • 该类及其迭代器实现了CollectionIterator接口的所有可选方法。
  • 这个类是Java Collections Framework的成员。
  • 从以下版本开始: 1.5

put(E e)方法

  • 在该队列的尾部插入指定的元素,如果队列已满,则等待空间变为可用。

take()

  • 检索并删除此队列的头,如有必要,等待元素可用。

sleep(long millis)

  • 使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行),具体取决于系统定时器和调度程序的精度和准确性。

2、代码实现

2.1Table类-阻塞的两种实现方式

Table类将用两种方式实现,一是原生的写法,二是调用ArrayBlockingQueue来实现。

  • Table原生实现
public class Table {
    private String[] cakes; //选择使用数组来存放:1.需求上我们不需要扩充容量 2. 原生比包装的效率高
    private int takeIndex; // 取蛋糕的索引
    private int putIndex;  // 放蛋糕的索引
    private int count;// 蛋糕的数量

    public Table(int count){
        cakes = new String[count];

    }
    public synchronized void put(String cake) throws InterruptedException{
        if(count >= cakes.length){
            wait();
        }
        cakes[putIndex] = cake;
        System.out.println(cake + " puts by: " +  Thread.currentThread().getName());
        putIndex = (putIndex + 1) % cakes.length;
        count++;
        notifyAll();
    }
    public synchronized String take() throws InterruptedException {
        if(count <=0 ){
            wait();
        }
        String cake = cakes[takeIndex];
        System.out.println(cake + " takes by: " +  Thread.currentThread().getName());
        takeIndex = (takeIndex + 1) % cakes.length;
        count--;
        notifyAll();
        return cake;
    }
}
  • 使用ArrayBlockingQueue来实现
//创建Table类继承ArrayBlockingQueue<E>类
public class Table  extends ArrayBlockingQueue<String> {
    //创建构造器
    public Table(int count) {
        super(count);
    }
    //创建put方法(放蛋糕)
    public void put(String cake) throws InterruptedException {
        super.put(cake);
        System.out.println(cake);

    }
    //创建get方法(取蛋糕)
    public String take() throws InterruptedException {
        String cake = super.take();
        System.out.println(cake + " 被" +  Thread.currentThread().getName()+"吃了");
        return cake;
    }
}
2.2MakerThread类
public class MakerThread extends Thread {
    private final Table table;
    private String cake;
    private static int id=1;
    private Random random = new Random();
    //创建构造器,对象名和table对象
    public MakerThread(String name, Table table){
    super(name);
    this.table = table;
    }
    //重写run方法,实现放蛋糕
    @Override
    public void run() {
        while (true){
            String cake = "[" +  getName() +"做了第"+generateId() + "个蛋糕]";
            try {
                table.put(cake);//放入蛋糕
                Thread.sleep(random.nextInt(1000));//随机阻塞
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    //生成蛋糕顺序id值
    private static synchronized int generateId(){
        return id++;
    }
}

2.3 EaterThread类
//编写EaterThread类,实现拿蛋糕
public class EaterThread extends Thread{
    private final Table table;
    private Random random = new Random();
//编写构造器,将name,table装进对象里
    public EaterThread(String name, Table table){
    super(name);
    this.table = table;
    }
    @Override
    public void run() {
        while(true) {
            try {
                String cake = table.take();//拿蛋糕
                Thread.sleep(random.nextInt(1000));//阻塞
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
2.4编写Main测试类
public class Main {
    public static void main(String[] args) {
        Table table = new Table(5);
        new MakerThread("一号小哥", table).start();
        new MakerThread("二号小哥", table).start();
        new MakerThread("三号小哥", table).start();
        new MakerThread("四号小哥", table).start();
        new EaterThread("1号小姐姐", table).start();
        new EaterThread("2号小姐姐", table).start();
        new EaterThread("3号小姐姐", table).start();
        new EaterThread("4号小姐姐", table).start();
    }
}
2.5运行结果:
[一号小哥做了第1个蛋糕]
[二号小哥做了第2个蛋糕]
[三号小哥做了第3个蛋糕]
[四号小哥做了第4个蛋糕]
[一号小哥做了第1个蛋糕]1号小姐姐吃了
[二号小哥做了第2个蛋糕]2号小姐姐吃了
[三号小哥做了第3个蛋糕]3号小姐姐吃了
[四号小哥做了第4个蛋糕]4号小姐姐吃了
[三号小哥做了第5个蛋糕]
[三号小哥做了第5个蛋糕]2号小姐姐吃了
[一号小哥做了第6个蛋糕]
[一号小哥做了第6个蛋糕]2号小姐姐吃了
2.6总结:线程的状态
  1. sleep方法是Thread类的静态方法,wait是Object的方法
  2. sleep不是必须要写在同步代码中,wait必须写在同步代码块中。
  3. sleep进入阻塞状态时,不会释放锁,wait进入阻塞中,会释放锁。
  4. wait可以通过notify/notifyAll方法,sleep到时候自己会醒来。
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值