Java中的阻塞式队列:BlockingQueue

73 阅读3分钟

1.阻塞队列

1.1 什么是阻塞队列

  阻塞队列是一种特殊的队列数据结构,具备在队列为空或已满时,自动阻塞等待的特性。它是多线程编程中常用的同步工具,用于在多个线程之间安全地传递和共享数据。

  具有以下特性:

  1. 当队列满的时候,继续入队列就会阻塞,直到有其他线程从队列中取走元素。
  2. 当队列空的时候,继续出队列也会阻塞,直到有其他线程往队列中插入元素。

  阻塞队列的一个典型应用场景就是 “生产者消费者模型” 。什么是 “生产者消费者模型” ?生产者消费者模型是一种并发编程模型,通过共享的缓冲区实现生产者线程生成数据并放入,消费者线程从中取出并处理的异步协作方式。

1.2 标准库中的阻塞队列

  Java标准库中阻塞队列:java.util.concurrent.BlockingQueue,这是一个接口,它里面提供的常用方法如下。

方法描述
add(E e)在队列尾部插入一个元素,如果队列已满,抛出IllegalStateException异常
offer(E e)在队列尾部插入一个元素,如果队列已满,返回false
put(E e)在队列尾部插入一个元素,如果队列已满,阻塞等待
remove()从队列头部移除并返回一个元素,如果队列为空,抛出NoSuchElementException异常
poll()从队列头部移除并返回一个元素,如果队列为空,返回null
take()从队列头部移除并返回一个元素,如果队列为空,阻塞等待
element()返回队列头部的元素,但不移除,如果队列为空,抛出NoSuchElementException异常
peek()返回队列头部的元素,但不移除,如果队列为空,返回null

  实现该接口的类有很多,常用的有:

  • LinkedBlockingQueue: 一个基于链表的阻塞队列。
  • ArrayBlockingQueue: 一个基于数组的有界阻塞队列。
  • PriorityBlockingQueue:一个支持优先级排序的阻塞队列。

  使用标准库中的阻塞队列来实现生产者消费者模型:

 public static void main(String[] args) {
        //固定数量为 30,生产数量超过30后阻塞。
        BlockingQueue<Integer> blockingQueue = new LinkedBlockingQueue<>(30);

        //消费者
        Thread consumer = new Thread(()->{
            while(true){
                try {
                    //开始消费
                    Integer result = blockingQueue.take();
                    System.out.println("消费元素:" + result);
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });


        //生产者
        Thread producer =new Thread(()->{
            int count = 0;
            while(true){
                try {
                    //开始生产
                    blockingQueue.put(count);
                    System.out.println("生产元素:" + count);
                    count++;
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        consumer.start();
        producer.start();
    }

1.3 阻塞队列的简单模拟实现

  这里只实现 put()take() 这个两个核心方法。

  1. 先实现一个非阻塞的队列,然后再来改进。
public class MyBlockingQueue {
	//队头
    private int head = 0;
    //队尾
    private int tail = 0;
    //有效长度
    private int useSize = 0;
    
    private final int[] items;

    public MyBlockingQueue(){
        items = new int[1000];
    }

    public MyBlockingQueue(int size){
        items = new int[size];
    }
    //入队
    public void put(int value){
        if(useSize == items.length){
            return;
        }
        items[tail++] = value;
        //循环队列的性质 也可写成:tail = tail % items.length
        if(tail >= items.length){
            tail = 0;
        }
        useSize++;
    }
    //出队
    public Integer take(){
        if(useSize == 0){
            return null;
        }

        Integer result = items[head++];
        if(head >= items.length){
            head = 0;
        }
        useSize--;
        return result;
    }
}
  1. put()take()加锁,对易变的变量加上volatile来保证内存可见性。
public class MyBlockingQueue {

    private volatile int head = 0;

    private volatile int tail = 0;

    private volatile int  useSize = 0;

    private final int[] items;

    public MyBlockingQueue(){
        items = new int[1000];
    }

    public MyBlockingQueue(int size){
        items = new int[size];
    }

    public void put(int value) throws InterruptedException {
        //对本对象加锁
        synchronized (this){
            while(useSize == items.length){
                this.wait();
            }
            items[tail++] = value;
            //循环队列的性质 也可写成:tail = tail % items.length
            if(tail >= items.length){
                tail = 0;
            }
            useSize++;
            //唤醒take中的 wait()
            this.notify();
        }
    }

    public Integer take() throws InterruptedException {
        int result = 0;
        synchronized (this){
            while(useSize == 0){
                this.wait();
            }
            result = items[head++];
            if(head >= items.length){
                head = 0;
            }
            useSize--;
            //唤醒 put 中的wait()
            this.notify();
        }
        return result;
    }
}

  这里的wait()为什么要套上一个while循环呢?在wait()的源码中有说明。

image.png