Java 生产者消费者模型的三种实现过程

Java 生产者消费者模型的三种实现过程

生产者一边在生产,消费者一边消耗。当库存满的时候生产者暂停生产,直到有空位;当库存空的时候消费者暂停消费,直到有产品。
在这里插入图片描述

关键点:

  1. 生产者和消费者都是在不断生产和消费的,是同时并发的,不应该等满了再消费 / 空了再生产。
  2. 前提条件是生产速度和消费速度不同。

设计方法:

  1. 生产者和消费者分别使用一个线程模拟。将其抽象分别继承Runnable接口,生产和消费的行为放在Runnablerun()方法中.
  2. 控制生产和消费的速度不同。可以使每次生产 / 每次消费 之间的间隔不同,达到 生产比消费快 或者 消费比生产快 的效果.
  3. 生产者拿到仓库锁,判断是否还有空位:
    • 如果没有空位,就让自己先别再去试图抢占锁了(将自己放入等待池,wait());
    • 如果有空位,就生产一个,然后唤醒(notify())等待池中的线程(包括先前因为仓库空而wait()掉的线程)来竞争锁。
  4. 消费者拿到仓库锁,判断是否还有可以消费的商品:
    • 如果没有商品了,就让自己先别再去试图抢占锁了(将自己放入等待池,wait());
    • 如果有空位,就消费一个,然后唤醒(notify())等待池中的线程(包括先前因为仓库满而wait()掉的线程)来竞争锁。

1.使用synchronized 关键字

使用waitnotify实现

public class ProducerAndConsumerModel {
    public static void main(String[] args) {
        list = new ArrayList<>();
        //启动生产者消费者线程
        new Thread(new Producer()).start();
        new Thread(new Consumer()).start();
    }

    static int MAX_SIZE = 10;//定义仓库容量
    static List<Integer> list;//定义仓库

    //定义生产者
    private static class Producer implements Runnable{

        @Override
        public void run() {
            while(true){
                try {
                    Thread.sleep(250);//生产效率:每250ms一个
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //第一步:获得锁
                synchronized(list){
                    //第二步:判断能不能生产
                    if(list.size() >= MAX_SIZE){
                        //进入此,代表库存已满,进入等待池,放弃锁,暂停线程
                        try {
                            list.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    //能生产:生产一个
                    list.add(1);
                    System.out.println("生产了一个产品,现在有" + list.size() + "个产品");
                    //第三步:唤醒消费者线程,试图消费
                    list.notifyAll();
                }

            }
        }
    }

    //定义消费者
    private static class Consumer implements Runnable{

        @Override
        public void run() {
            while(true){
                try {
                    Thread.sleep(500);//消费效率:每500ms消费一个
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //第一步:获得锁
                synchronized (list){
                    //第二步:判断能不能消费,即仓库是否为空
                    if(list.size() == 0){
                        try {
                            //如果发现没有商品:就进入等待池,释放锁,线程暂停
                            list.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    //能消费:消费一个
                    list.remove(0);
                    System.out.println("消费了一个产品,现在还有" + list.size() + "个产品");
                    //唤醒生产者线程,试图生产
                    list.notifyAll();
                }
            }


        }

注意:我们在这里使用的notifyAll()这个方法,为什么不能用notify(),也就是随便叫醒一个消费者呢?

答案是不可以,使用 notify()是叫醒 LOCK 阻塞队列里面的任意一个线程,假如此时我们的临界区域已经满了,此时唤醒的是一个生产者线程,就会导致死锁,所以我们在这里采用的是notifyAll()这个方法,意思就是唤醒阻塞队列里面的全部线程,这样某一个消费者就可以去取出临界区里面的产品,从而避免死锁的发生。

2.使用ReenterantLock

ReenterantLocksynchronized类似,synchronized在语句块结束后会自动释放锁,ReenterantLock需要unlock()ReenterantLock还支持公平锁(等待最久的先拿锁)。

使用awaitsingalAll实现

public class ProducerAndConsumerModelReentrantLock {
    public static void main(String[] args) {
        list = new ArrayList<>();
        ableToProducer = lock.newCondition();
        ableToConsumer = lock.newCondition();

        new Thread(new Producer()).start();
        new Thread(new Consumer()).start();
    }
    static int MAX_SIZE = 10;
    static List<Integer> list;
    static ReentrantLock lock = new ReentrantLock(true);//指定为公平锁
    static Condition ableToProducer,ableToConsumer;


    //生产者
    private static class Producer implements Runnable{

        @Override
        public void run() {
            while(true){
                try {
                    Thread.sleep(250);//250ms生产一个
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //第一步:获得锁
                lock.lock();
               try {
                   //第二步:判断能不能生产
                   if(list.size() >= MAX_SIZE){
                       try {
                           //进入此,代表库存已满,进入等待池,放弃锁,暂停线程
                           ableToProducer.await();
                       } catch (InterruptedException e) {
                           e.printStackTrace();
                       }
                   }
                   //能生产:生产一个
                   list.add(1);
                   System.out.println("生产了一个产品,现在有" + list.size() + "个产品");
                   //第三步:给消费者线程一个信号
                   ableToConsumer.signalAll();
               }finally {
                   lock.unlock();
               }
            }
        }
    }

    //消费者
    private static class Consumer implements Runnable{

        @Override
        public void run() {
            while(true){
                try {
                    Thread.sleep(500);//每500ms消费一个
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //第一步:获得锁
                lock.lock();
                try{
                    //第二步:判断能不能消费,即仓库是否为空
                    if(list.size() == 0){
                        //如果发现没有商品:就进入等待池,释放锁,线程暂停
                        try {
                            ableToConsumer.await();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    //能消费:消费一个
                    list.remove(0);
                    System.out.println("消费了一个产品,现在还有" + list.size() + "个产品");
                    //给生产者一个信号
                    ableToProducer.signalAll();
                }finally {
                    lock.unlock();
                }
            }
        }
    }
}

synchronized 相比之下,一个 lock 我们可以生成多个condition,换句话说synchronized就像是只有一个 conditionReentrantLock,所以 后者比前者更加的灵活,可以进行选择性的通知,但是也较为麻烦,因为每次都得手动地关闭锁,所以我们每次得尝试在finally 里面关闭锁。

3. 使用BlockingQueue

阻塞队列本身就是当队满的时候阻塞线程,故直接使用就可以实现生产者/消费者模型。
阻塞队列内部也是使用ReenterantLock实现的。

public class ProducerAndConsumerModelUseBlockingQueue {
    public static void main(String[] args) {
        queue =new ArrayBlockingQueue<Integer>(MAX_SIZE);
        new Thread(new Producer()).start();
        new Thread(new Consumer()).start();
    }

    static BlockingQueue<Integer> queue;//定义一个队列
    static int MAX_SIZE = 10;//定义最大容量

    //生产者
    private static class Producer implements Runnable{

        @Override
        public void run() {
            while(true){
                try {
                    Thread.sleep(250);//250ms生产一个
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                try {
                    //生产一个
                    queue.put(1);
                    System.out.println("生产了一个产品,现在有"+queue.size()+"个产品。");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    //消费者
    private static class Consumer implements Runnable{

        @Override
        public void run() {
            while(true) {
                try {
                    Thread.sleep(500);//每500ms消费一个
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                try {
                    //消费一个
                    queue.take();
                    System.out.println("消费了一个产品,现在有"+queue.size()+"个产品。");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值