生产者和消费者问题

1. 概述

生产者-消费者是一个经典的多线程协作问题。所谓生产者-消费者问题,实际上是包含两类线程,一种是生产者线程,用于生产数据,另一种是消费者线程,用于消费数据。为了解耦生产者和消费者的关系,通常会采用共享的数据区域。生产者往共享区域放数据,无需关心消费者的行为。消费者从共享区域取数据,无需关心生产者的行为。同时,生产者和消费者之间应该具备以下功能:

  1. 如果共享数据区已满,阻塞生产者继续生产数据到共享数据区;
  2. 如果共享数据区为空,阻塞消费者线程消费共享数据区的数据;

接下来,我们介绍几种方法来完成生产者-消费者模型。

2. wait/notifyAll模型

wait和notify方法是Object类自带的方法,用于线程之间的阻塞和唤醒。我们通过下面的代码来具体看看wait/notify模型如何使用:

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TestProducerAndConsumer1 {

    public static void main(String[] args) {
        List<Integer> storage = new ArrayList<Integer>();
        ExecutorService producerExecutorService = Executors.newFixedThreadPool(2);
        ExecutorService consumerExecutorService = Executors.newFixedThreadPool(2);
        for (int i = 0; i < 10; i++) {
            producerExecutorService.execute(new Producer(storage, 1));  // 生产者
        }
        for (int i = 0; i < 5; i++) {
            consumerExecutorService.execute(new Consumer(storage));   // 消费者
        }
    }

    static class Producer extends Thread {
        List<Integer> storage;   // 仓库
        Random random;

        int maxSize;   // 最大容量

        public Producer(List<Integer> storage, int maxSize) {
            this.storage = storage;
            random = new Random();
            this.maxSize = maxSize;
        }

        @Override
        public void run() {
            synchronized (storage) {
                try {
                    while (storage.size() == maxSize) {   // 达到最大容量,生产者进入等待
                        System.out.println("库存满了,生产者等待消耗商品");
                        storage.wait();
                    }
                    int randNum = random.nextInt(1000);
                    storage.add(randNum);
                    System.out.println("生产者生产商品:" + randNum);
                    storage.notifyAll();   // 唤醒等待的消费者
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    static class Consumer extends Thread {
        List<Integer> storage;

        public Consumer(List<Integer> storage) {
            this.storage = storage;
        }

        @Override
        public void run() {
            synchronized (storage) {
                try {
                    while (storage.isEmpty()) {   // 库存空了,消费者进入等待
                        System.out.println("库存空了,消费者等待补充商品");
                        storage.wait();
                    }
                    int randNum = storage.remove(0);
                    System.out.println("消费者消费商品" + randNum);
                    storage.notifyAll();   // 唤醒等待的生产者
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

运行结果:

生产者生产商品:634
库存满了,生产者等待消耗商品
库存满了,生产者等待消耗商品
消费者消费商品634
生产者生产商品:689
库存满了,生产者等待消耗商品
消费者消费商品689
生产者生产商品:448
库存满了,生产者等待消耗商品
消费者消费商品448
生产者生产商品:957
库存满了,生产者等待消耗商品
消费者消费商品957
生产者生产商品:939
库存满了,生产者等待消耗商品
消费者消费商品939
生产者生产商品:225
库存满了,生产者等待消耗商品
库存满了,生产者等待消耗商品

通过上面的代码我们实现了生产者-消费者模型,也完成了必须具备的两个功能。但是有一点问题需要注意下:在等待队列中,排在队首的可能是生产者,也可能是消费者。如果使用notify方法来唤醒,就有可能出现生产者只唤醒生产者,消费者只唤醒消费者,这就会出现问题。所以我们在代码中使用notifyAll方法+业务逻辑判断地方式唤醒所有线程,然后利用while循环来判断是否真的符合解除阻塞的条件。

3. Lock中Condition的await/signalAll模型

我们以ReentrantLock中的Condition条件队列来举例await/signalAll模型:

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class TestProducerAndConsumer2 {

    public static void main(String[] args) {
        List<Integer> storage = new ArrayList<Integer>();
        ReentrantLock reentrantLock = new ReentrantLock();
        Condition condition = reentrantLock.newCondition(); // condition条件队列
        ExecutorService producerExecutorService = Executors.newFixedThreadPool(2);
        ExecutorService consumerExecutorService = Executors.newFixedThreadPool(2);
        for (int i = 0; i < 10; i++) {
            producerExecutorService.execute(new Producer(storage, reentrantLock, condition,2));  // 生产者线程
        }
        for (int i = 0; i < 5; i++) {
            consumerExecutorService.execute(new Consumer(storage, reentrantLock, condition));   // 消费者线程
        }
    }

    static class Producer extends Thread {
        List<Integer> storage;
        Random random;

        ReentrantLock reentrantLock;

        Condition condition;

        int maxSize;

        public Producer(List<Integer> storage, ReentrantLock reentrantLock, Condition condition,int maxSize) {
            this.reentrantLock = reentrantLock;
            this.storage = storage;
            this.condition = condition;
            this.maxSize = maxSize;   // 仓库最大容量
            random = new Random();
        }

        @Override
        public void run() {
            reentrantLock.lock();
            try {
                while(storage.size()==maxSize){
                    System.out.println("库存满了,生产者等待消耗商品");
                    condition.await();  // 仓库满了,生产者进入等待
                }
                int randNum = random.nextInt(1000);
                storage.add(randNum);
                System.out.println("生产者生产商品:" + randNum);
                condition.signalAll();  // 仓库补货了,唤醒消费者
            }catch (Exception e){
                e.printStackTrace();
            } finally {
                reentrantLock.unlock();
            }
        }
    }

    static class Consumer extends Thread {
        ReentrantLock reentrantLock;
        Condition condition;
        List<Integer> storage;

        public Consumer(List<Integer> storage, ReentrantLock reentrantLock, Condition condition) {
            this.reentrantLock = reentrantLock;
            this.storage = storage;
            this.condition = condition;
        }

        @Override
        public void run() {
            reentrantLock.lock();
            try {
                while (storage.isEmpty()) {
                    System.out.println("库存空了,消费者等待补充商品");
                    condition.await();  // 仓库空了,消费者进入等待
                }
                int randNum = storage.remove(0);
                System.out.println("消费者消费商品" + randNum);
                condition.signalAll();  // 消费商品了,唤醒生产者
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } finally {
                reentrantLock.unlock();
            }
        }
    }
}

输出结果:

生产者生产商品:340
生产者生产商品:491
库存满了,生产者等待消耗商品
库存满了,生产者等待消耗商品
消费者消费商品340
消费者消费商品491
库存空了,消费者等待补充商品
生产者生产商品:888
生产者生产商品:250
库存满了,生产者等待消耗商品
消费者消费商品888
消费者消费商品250
生产者生产商品:357
生产者生产商品:135
库存满了,生产者等待消耗商品
库存满了,生产者等待消耗商品
消费者消费商品357
生产者生产商品:227
库存满了,生产者等待消耗商品
库存满了,生产者等待消耗商品

实际上await/signalAll模型和wait/notifyAll模型是很相似的,主要是await/signalAll模型需要借助于Lock。同样在这里我们采用了signalAll方法,没有采用signal方法。

4. BlockingQueue模型

这里我们利用的是阻塞队列的方式,分别采用put()和take()方法,当队列满或者队列空的时候,生产者和消费者进程就会被阻塞。如下面的例子所示:

import java.util.Random;
import java.util.concurrent.*;

public class TestProducerAndConsumer3 {
    public static void main(String[] args) {
        BlockingDeque<Integer> blockingDeque = new LinkedBlockingDeque(2);  // 定义仓库容量为2
        ExecutorService producerExecutorService = Executors.newFixedThreadPool(2);
        ExecutorService consumerExecutorService = Executors.newFixedThreadPool(2);
        for (int i = 0; i < 10; i++) {
            producerExecutorService.execute(new Producer(blockingDeque));  // 生产者
        }
        for (int i = 0; i < 5; i++) {
            consumerExecutorService.execute(new Consumer(blockingDeque));  // 消费者
        }
    }

    static class Producer extends Thread {
        BlockingDeque<Integer> blockingDeque;
        Random random;

        public Producer(BlockingDeque blockingDeque) {
            this.blockingDeque = blockingDeque;
            random = new Random();
        }

        @Override
        public void run() {
            try {
                int randNum = random.nextInt(1000);
                blockingDeque.put(randNum);   //生产商品
                System.out.println("生产者生产商品:" + randNum);
                Thread.sleep(500);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }

    static class Consumer extends Thread {

        BlockingDeque<Integer> blockingDeque;

        public Consumer(BlockingDeque<Integer> blockingDeque) {
            this.blockingDeque = blockingDeque;
        }

        @Override
        public void run() {
            try {
                int randNum = blockingDeque.take();  // 消费商品
                System.out.println("消费者消费商品" + randNum);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

输出结果:

生产者生产商品:904
生产者生产商品:28
消费者消费商品904
消费者消费商品28
生产者生产商品:298
消费者消费商品160
消费者消费商品298
生产者生产商品:160
生产者生产商品:22
消费者消费商品22
生产者生产商品:102
生产者生产商品:561

采用BlockingQueue的好处在于不需要我们自己定义判断仓库满或空的逻辑,BlockingQueue内部会自己实现好,简化了我们的工作。

参考文章:
一篇文章,让你彻底弄懂生产者–消费者问题

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
生产者消费者问题是一种经典的同步问题,通常应用于操作系统中。该问题描述了生产者消费者共享一个有限大小的缓冲区,生产者将数据放入缓冲区,消费者从缓冲区中取出数据。生产者消费者必须同步,以避免生产者试图向已满的缓冲区中添加数据,或消费者试图从空缓冲区中取出数据。 生产者消费者问题可以使用信号量或管程等同步机制来解决。其中,信号量是一种简单的同步机制,可以用于控制并发访问共享资源。在生产者消费者问题中,可以使用两个信号量来控制缓冲区的访问:一个用于表示缓冲区中还可以放置数据的空闲槽位数量,另一个用于表示缓冲区中已经存放的数据数量。 具体来说,当生产者想要向缓冲区中添加数据时,它需要获取空闲槽位的信号量,如果没有空闲槽位,则需要等待。当生产者成功添加数据后,它需要释放空闲槽位的信号量,同时通知消费者有新的数据可用。当消费者想要从缓冲区中取出数据时,它需要获取已经存放的数据的信号量,如果没有数据,则需要等待。当消费者成功取出数据后,它需要释放已经存放的数据的信号量,同时通知生产者有新的空闲槽位可用。 通过使用信号量等同步机制,可以有效地解决生产者消费者问题,并保证生产者消费者之间的同步和互斥。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值