如何实现一个阻塞队列?

阻塞队列(BlockingQueue):
是一个支持两个附加操作的队列。这两个附加的操作支持阻塞的插入和移除方法

1)支持阻塞的插入方法:意思是当队列满时,队列会阻塞插入元素的线程,直到队列不满。
2)支持阻塞的移除方法:意思是在队列为空时,获取元素的线程会等待队列变为非空

阻塞队列常用于生产者和消费者的场景,生产者是向队列里添加元素的线程,消费者是从队列里取元素的线程。阻塞队列就是生产者用来存放元素、消费者用来获取元素的容器。

阻塞队列的具体实现:
普通的队列是怎么实现的?

  1. 基于链表
  2. 基于数组 (今天我使用的)

代码实现:

    static class BlockingQueue {
        private int[] array = new int[1000];
        private volatile int head = 0;
        private volatile int tail = 0;
        // head 和 tail 构成的是一个前闭后开区间.
        // 当两者重合的时候, 可能是表示队列空, 也可能是表示队列满.
        // 为了区分空还是满, 就需要额外引入一个 size 来表示.
        private volatile int size = 0;

        // 队列的基本操作
        // 1. 入队列
        // 2. 出队列
        // 3. 取队首元素
        // 针对阻塞队列来说, 只提供前两个操作, 不支持取队首元素.

        // 阻塞版本的入队列, 为了和之前的版本区分, 用了不同的名字.
        public void put(int value) throws InterruptedException {
            synchronized (this) {
               if(size == array.length) {
                    wait();
                }

                // 把 value 放到队尾即可
                array[tail] = value;
                tail++;
                if (tail == array.length) {
                    tail = 0;
                }
                size++;

                notify();
            }
        }

        // 阻塞版本的出队列, 用了不同的名字
        public int take() throws InterruptedException {
            int ret = -1;
            synchronized (this) {
                if (size == 0) {
                    this.wait();
                }

                ret = array[head];
                head++;
                if (head == array.length) {
                    head = 0;
                }
                size--;
               
                notify();
            }
            return ret;
        }
    }

问题来了!
1.下面两个wait操作有没有可能同时被触发?
答案是不可能的,因为两个wait的触发条件一个是队列为满,一个是队列为空。
在这里插入图片描述在这里插入图片描述
2.如果是多个线程wait,那么notify会唤醒哪个线程呢?
答案是随机的,因为这由操作系统调度器说了算。
3.如果把代码中的notify换成notifyAll是否可行?
答案:不行,会出现问题。
在这里插入图片描述
在这里插入图片描述
4.如果坚持改成notifyAll代码该如何修改?
把判断条件if改成while
在这里插入图片描述
在这里插入图片描述

测试代码:

  public static void main(String[] args) throws InterruptedException {
        BlockingQueue blockingQueue = new BlockingQueue();
        // 搞两个线程, 分别模拟生产者和消费者.
        // 第一次, 让给消费者消费的快一些, 生产者生产的慢一些.
        // 此时就预期看到, 消费者线程会阻塞等待. 每次有新生产的元素的时候, 消费者才能消费
        // 第二次, 让消费者消费的慢一些, 生产者生产的快一些.
        // 此时就预期看到, 生产者线程刚开始的时候会快速的往队列中插入元素, 插入满了之后就会阻塞等待.
        // 随后消费者线程每次消费一个元素, 生产者才能生产新的元素.
         Thread producer = new Thread(){
             @Override
             public void run() {
                 for(int i = 0; i<1000; i++) {
                     try {
                         blockingQueue.put(i);
                         System.out.println("生产元素:"+i);
                         Thread.sleep(500);
                     } catch (InterruptedException e) {
                         e.printStackTrace();
                     }
                 }
             }
         };
         producer.start();
        Thread consumer = new Thread() {
            @Override
            public void run() {
                while (true) {
                    try {
                        int ret = blockingQueue.take();
                        System.out.println("消费元素: " + ret);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        consumer.start();
    }

测试结果:
快慢由sleep控制
1.先让生产的快一点
在这里插入图片描述
2.让消费的快一点
在这里插入图片描述
总结:
阻塞队列是基于普通队列实现的,与多线程联系到一起。主要是对wait()与notify()操作的应用。
如果有错误希望大家多多指正。

猜你喜欢

转载自blog.csdn.net/weixin_45755718/article/details/106835215