生产者-消费者模式

生产者-消费者模式

  • 生产者-消费者模式是一种经典的多线程设计模式,它为多线程之间的协作提供了良好的解决方案。利用共享内存缓冲区来进行通信。它有以下两个好处:
    1. 生产者和消费者之间不直接进行通讯,而是通过共享内存缓冲区,从而将生产者和消费者进行了解耦
    2. 由于共享内存缓冲区的存在,允许生产者和消费者在执行速度上存在时间差异,平衡了生产者和消费者的处理能力
实现方式
在Java中一共有四种方法支持同步,其中前三个是同步方法,一个是管道方法。
    1. wait() / notify()方法
    2. await() / signal()方法
    3. BlockingQueue阻塞队列方法
    4. PipedInputStream / PipedOutputStream

这里主要介绍第1,2和第3种

  • wait()/notify()方法

    import java.util.LinkedList;
    import java.util.concurrent.TimeUnit;
    
    public class MyContainer1<T> {
        final private LinkedList<T> lists = new LinkedList<>();
        final private int MAX = 10; //最多10个元素
        private int count = 0;
    
    
        public synchronized void put(T t) {
            while(lists.size() == MAX) { //想想为什么用while而不是用if?
                try {
                    this.wait(); //effective java
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
    
            lists.add(t);
            ++count;
            this.notifyAll(); //通知消费者线程进行消费
        }
    
        public synchronized T get() {
            T t = null;
            while(lists.size() == 0) {
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            t = lists.removeFirst();
            count --;
            this.notifyAll(); //通知生产者进行生产
            return t;
        }
    
        public static void main(String[] args) {
            MyContainer1<String> c = new MyContainer1<>();
            //启动消费者线程
            for(int i=0; i<10; i++) {
                new Thread(()->{
                    for(int j=0; j<5; j++) System.out.println(c.get());
                }, "c" + i).start();
            }
    
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } 
    
            //启动生产者线程
            for(int i=0; i<2; i++) {
                new Thread(()->{
                    for(int j=0; j<25; j++) c.put(Thread.currentThread().getName() + " " + j);
                }, "p" + i).start();
            }
        }
    }
    
  • await()和signal()方法

    public class MyContainer2<T> {
        final private LinkedList<T> lists = new LinkedList<>();
        final private int MAX = 10; //最多10个元素
        private int count = 0;
    
        private Lock lock = new ReentrantLock();
        private Condition producer = lock.newCondition();
        private Condition consumer = lock.newCondition();
    
        public void put(T t) {
            try {
                lock.lock();
                while(lists.size() == MAX) { //想想为什么用while而不是用if?
                    producer.await();
                }
    
                lists.add(t);
                ++count;
                consumer.signalAll(); //通知消费者线程进行消费
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    
        public T get() {
            T t = null;
            try {
                lock.lock();
                while(lists.size() == 0) {
                    consumer.await();
                }
                t = lists.removeFirst();
                count --;
                producer.signalAll(); //通知生产者进行生产
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
            return t;
        }
    
        public static void main(String[] args) {
            MyContainer2<String> c = new MyContainer2<>();
            //启动消费者线程
            for(int i=0; i<10; i++) {
                new Thread(()->{
                    for(int j=0; j<5; j++) System.out.println(c.get());
                }, "c" + i).start();
            }
    
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            //启动生产者线程
            for(int i=0; i<2; i++) {
                new Thread(()->{
                    for(int j=0; j<25; j++) c.put(Thread.currentThread().getName() + " " + j);
                }, "p" + i).start();
            }
        }
    }
    
  • 针对以上两种方法,面试中常问一下两个问题

    1. 为什么用notifyAll,而不用notify

      以生产者为例,调用notify只能叫醒一个线程,该叫醒的线程可能还是生产者,但是本意是生产者生产了一个产品,让消费者唤醒来消费该产品,所以应该使用notifyAll,唤醒所有线程

    2. 判断时为什么用while而不用if 还是以生产者线程为例,当生产者发现内存缓冲区满的时候,调用wait()方法等待,当消费者消费掉一个产品后,生产者线程被唤醒,当一个生产者线程从wait()出准备执行时,另一个线程获得了锁,并成功执行,放入了一个产品;这时这个生产者线程拿到锁后,如果是if的话,直接从wait()后开始执行,发现已放不下,会抛出异常。但如果换成while,它会再执行判断,发现共享内存缓冲区已满,则又会等待
  • 这里还想说下synchronized和ReenTrantLock之间的区别:

    1. Synchronized可以用在方法上,也可用于代码块上;而ReenTrantLock只能用于特定代码块上
    2. Synchronized不需要手动释放锁,而RennTrantLock既要手动上锁还要手动释放锁
    3. ReenTrantLock等待可中断,当一个线程长期不释放锁时,正在等待的线程可以调用lockInterruptibly()
    方法对该线程进行打断
    4. Synchronized为非公平锁,而ReenTrantLock可以指定为公平锁,根据申请锁的时间依次获得锁
    5. 一个ReenTrantLock可以同时绑定多个Condition对象,而Synchronized要和多于一个的条件关联时,要额外添加锁,
    因此Condition能够更加精确的控制多线程的休眠与唤醒
    
  • 阻塞队列的方法

  • producer

        public class Producer implements Runnable{
        BlockingQueue<String> queue;
    
        public Producer(BlockingQueue<String> queue){
            this.queue = queue;
        }
    
        @Override
        public void run() {
            try{
                String product_name = "a product-"+Thread.currentThread().getName();
                System.out.println("I have produced:"+Thread.currentThread().getName());
                queue.put(product_name); //如果队满,则阻塞
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
    
  • consumer

        public class consumer implements Runnable{
        BlockingQueue<String> blockingQueue;
    
        public consumer(BlockingQueue<String> blockingQueue){
            this.blockingQueue = blockingQueue;
        }
    
    
        @Override
        public void run() {
            try {
                String product_name = blockingQueue.take(); //如果队空,则阻塞
                System.out.println("消费了:"+product_name);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        }
    
  • test

    public class test_producer {
    public static void main(String[] args) {
        BlockingQueue<String> queue = new LinkedBlockingDeque<>(2);
        //BlockingQueue<String> queue = new ArrayBlockingQueue<String>(2);
        // LinkedBlockingDeque 默认大小为Integer.MAX_VALUE
        for (int i = 0; i < 5; i++) {
            new Thread(new Producer(queue), "Producer"+i).start();
            new Thread(new consumer(queue), "Consumer"+i).start();
        }
    }
    }


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值