生产者-消费者模型

阿里面试:如何写一个缓冲区?

缓冲区是可以被多个线程存数据和取数据,那么这个时候可能会发生线程安全问题,比如不能同时对缓冲区进行存或取,缓冲区没有数据就不能取,缓冲区满了就不能存,这其实就是生产者消费者模型。

注意,这与读者-写者问题的本质不同在于,读者可以有多个,他们可以一起读,不会发生安全问题,只是读和写是互斥的。而生产者是对数据进行增加,消费者对数据进行删除,是会发生安全问题的,生产者与生产者、生产者与消费者、消费者与消费者之间都应该互斥
在这里插入图片描述

简易版生产者—消费者模型

所谓简易版指的是生产者和消费者都只有一个线程

生产者

生产者要做的事就是往缓冲区存数据

public class Producer extends Thread{
    private Buffer<Integer> buffer;

    public Producer(Buffer<Integer> buffer) {
        this.buffer = buffer;
    }

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("生产者正在生产数据"+i);
            buffer.add(i);
        }
    }
}

消费者

public class Consumer extends Thread{
    private Buffer<Integer> buffer;

    public Consumer(Buffer<Integer> buffer) {
        this.buffer = buffer;
    }

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            Integer data = buffer.pull();
            System.out.println("消费者拿到数据:"+data);
        }
    }
}

缓冲区

public class Buffer<T> {
    private Queue<T> bufferData = new LinkedList<>();
    private int size;

    public Buffer(int size) {
        this.size = size;
    }

    public synchronized void add(T data){
        if(bufferData.size() >= size){
            System.out.println("缓冲区已满,请等待消费者消费数据");
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        bufferData.offer(data);
        notify();
    }

    public synchronized T pull(){
        if(bufferData.size() == 0){
            System.out.println("缓冲区无数据,请等待生产者生产数据");
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        T data = bufferData.poll();
        notify();
        return data;
    }
}

测试

public class ProducerConsumer {
    public static void main(String[] args) {
        Buffer<Integer> buffer = new Buffer<>(3);
        new Producer(buffer).start();
        new Consumer(buffer).start();
    }
}

测试结果

在这里插入图片描述

进阶版本

那其实我们平时实际场景中,生产者和消费者都可以有多个,那就需要对上面的代码进行更改

我们看看上面缓冲区的存数据方法的问题

    public synchronized void add(T data){
        if(bufferData.size() >= size){
            wait();
        bufferData.offer(data);
        notify();
    }

因为有很多线程,notify应该变成notifyAll,我们希望缓冲区满的时候唤醒的是消费者线程,让他来消费。而notify只能唤醒一个线程,你不知道唤醒的这个线程到底是生产者还是消费者,notifyAll可以唤醒所有线程。

那么if应该也要变成while(),因为你notifyAll也会唤醒其他生产者,而这个时候缓冲区可能还是满的,所以应该是循环等待,不然就出线程安全问题了

这就是改进版代码,同样地取数据也应该改成这样

    public synchronized void add(T data){
        while(bufferData.size() >= size){
            wait();
        bufferData.offer(data);
        notifyAll();
    }

而上面的方法,将生产者消费者糅杂在一起了,一次性全都唤醒会导致大量的锁竞争,而这非常消耗系统资源。我们的想法是缓冲区满了,生产者不生产,我只想唤醒消费者去竞争锁,我不想让生产者也唤醒,那么怎么处理呢?

那么我们可以使用Condition条件,来将消费者和生产者加入到不同的等待队列,这样就解决了,这就要用到ReentrantLock

import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Buffer<T> {
    private Queue<T> bufferData = new LinkedList<>();
    Lock lock;
    Condition producerCondition;
    Condition consumerCondition;

    private int size;

    public Buffer(int size) {
        lock = new ReentrantLock();
        producerCondition = lock.newCondition();
        consumerCondition = lock.newCondition();
        this.size = size;
    }

    public void add(T data){
        try {
            lock.lock();
            while(bufferData.size() >= size){
                System.out.println("缓冲区已满,请等待消费者消费数据");
                producerCondition.await();
            }
            System.out.println("生产者"+Thread.currentThread().getName()+"正在生产数据"+data);
            bufferData.offer(data);
            consumerCondition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        finally {
            lock.unlock();
        }
    }

    public synchronized T pull(){
        T data = null;
        try {
            lock.lock();
            while(bufferData.size() == 0){
                System.out.println("缓冲区无数据,请等待生产者生产数据");
                consumerCondition.await();
            }
            data = bufferData.poll();
            System.out.println("消费者"+Thread.currentThread().getName()+"拿到数据:"+data);
            producerCondition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        finally {
            lock.unlock();
        }
        return data;
    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值