手写一个简单的生产者-消费者模型

194 阅读2分钟

一、互斥与同步

  • 临界资源互斥访问:任一时刻只有一个线程可以访问阻塞队列,对其进行添加/删除元素的操作
  • 线程的同步:若队列已满,生产者线程挂起;若队列已空;消费者线程挂起;生产者生产元素后唤醒消费者、消费者消费元素后唤醒生产者

二、实现分析

  • 对阻塞队列加锁的实现互斥访问
  • wait/notifyAll实现线程的同步

三、代码实现

@Slf4j
public class Producer implements Runnable{
    // 阻塞队列
    Queue<Integer> queue;

    //队列大小
    private int MAX_SIZE;

    // 随机生产元素
    Random random = new Random();

    public Producer(Queue queue,int MAX_SIZE){
        this.queue = queue;
        this.MAX_SIZE = MAX_SIZE;
    }

    @Override
    public void run() {
        synchronized (queue){
            // while避免虚假唤醒
            while (queue.size() == MAX_SIZE){
                try {
                    // 需要挂起当前线程,释放queue上的锁
                    // 让消费者线程可以获取该锁,并获取队列中的元素
                    queue.wait();
                    log.debug("队列已满,需要挂起....");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            // 队列未满则生成元素,
            int i = random.nextInt();
            queue.offer(i);
            log.debug("队列未满,生产者生产元素{}",i);
            log.debug("当前队列大小为{}",queue.size());
            // 唤醒消费者线程
            queue.notifyAll();
            log.debug("唤醒消费者/生产者线程");
        }
    }
}
@Slf4j
public class Consumer implements Runnable{
    // 阻塞队列
    Queue<Integer> queue;


    public Consumer(Queue queue){
        this.queue = queue;
    }

    @Override
    public void run() {
        synchronized (queue){
            // while避免虚假唤醒
            while (queue.size() == 0){
                try {
                    // 需要挂起当前线程,释放queue上的锁
                    // 让生产者线程可以获取该锁,并生产元素
                    queue.wait();
                    log.debug("队列已空,消费者线程需要挂起....");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            // 队列不为0则获取元素
            int  i = queue.poll();
            log.debug("消费者线程消费了元素{}",i);
            log.debug("当前队列大小为{}",queue.size());
            // 唤醒消费者线程
            queue.notifyAll();
            log.debug("唤醒消费者/生产者线程");
        }
    }
}
public class Test1 {
    public static void main(String[] args) throws InterruptedException {
        Random random = new Random();
        Queue<Integer> queue = new LinkedList<>();
        int MAX_SIZE=10;
        for(int i=0;i<10;i++){
            queue.offer(random.nextInt(500));
        }
        for (int i=0;i<5;i++){
            new Thread(new Producer(queue,MAX_SIZE){

            }).start();
            new Thread(new Consumer(queue){

            }).start();
        }
    }
}

四、运行结果

image.png

五、缺陷和改进

  • 可以看到在多个生产者和消费者的情况下,notifyAll会唤醒两种线程。也就是说,有可能消费者线程运行之后,运行的还是消费者线程。

2d222eeb976d03fc7948da216dec5d8.png

后续可以采用condition进行改进