BlockingQueue(阻塞队列)

133 阅读3分钟

什么是 BlockingQueue

  BlockingQueue 继承了 Queue 接口,是队列的一种。Queue 和 BlockingQueue 都是在 Java 5 中加入的。 BlockingQueue 是线程安全的。简而言之,阻塞队列是生产者用来存放元素、消费者获取元素的容器。

阻塞队列的方法

// 会阻塞的方法
put():插入元素,但是如果队列已满,那么就阻塞,直到队列有了空间,再进行插入。
take():获取并移除队列的头节点,一旦执行 take 的时候,队列无数据就阻塞,直到队列里有数据。

// 会抛出异常的方法
add():插入发现队列满了,抛出异常
remove():删除数据,队列无数据抛出异常
element():返回队列头元素,队列为空抛出异常

// 会返回特殊值的方法
offer():添加元素,失败返回false,否则返回true
poll():获取不到或队列没数据返回 null,成功取出之后删除
peck():获取不到或队列没数据返回 null,成功取出之后不删除

ArrayBlockingQueue

一个可以指定队列长度的有界阻塞队列,先进先出。
内部维护的是一个固定长度的数组。
使用一个锁(ReentrantLock)和 两个 Condition( put 、take ), put 、take 都需要先获取锁,所以在高并发情况下没有 LinkedBlackingQueue 速度快,但占用开销小,因为内存是提前创建的,无需扩容。

LinkedBlockingQueue

一个可以指定队列长度的有界阻塞队列,先进先出。
内部维护的是一个单向链表。
使用两个锁(ReentrantLock)和 Condition 还有 AtomicInteger 类型的 count 保证线程安全,入队需要获取 put 锁,出队需要获取 take 锁,入队出队不阻塞。

put 方法

    public void put(E e) throws InterruptedException {
        if (e == null) throw new NullPointerException();
        // Note: convention in all put/take/etc is to preset local var
        // holding count negative to indicate failure unless set.
        int c = -1;
        Node<E> node = new Node<E>(e);
        final ReentrantLock putLock = this.putLock;
        final AtomicInteger count = this.count;
        // 获取 put 锁
        putLock.lockInterruptibly();
        try {
            /*
             * Note that count is used in wait guard even though it is
             * not protected by lock. This works because count can
             * only decrease at this point (all other puts are shut
             * out by lock), and we (or some other waiting put) are
             * signalled if it ever changes from capacity. Similarly
             * for all other uses of count in other wait guards.
             */
            // 满了,进入等待队列,等待唤醒
            while (count.get() == capacity) {
                notFull.await();
            }
            // 入队,放到队尾
            enqueue(node);
            // 原子的把队列里数量+1,但是返回的值是原来的值
            c = count.getAndIncrement();
            // 判断队列里是否有空间,有的话试着唤醒一个等待队列里的线程
            if (c + 1 < capacity)
                notFull.signal();
        } finally {
            // 释放 put 锁
            putLock.unlock();
        }
        // = 0 表示之前没值,现在有值了,获取 take 锁,然后试着唤醒一个 take 线程进行消费
        // 这个线程值没取完的话,被唤醒的这个线程会唤醒别的等待队列里的 take 线程进行消费
        if (c == 0)
            signalNotEmpty();
    }

take 方法

    public E take() throws InterruptedException {
        E x;
        int c = -1;
        final AtomicInteger count = this.count;
        final ReentrantLock takeLock = this.takeLock;
        // 获取 take 锁
        takeLock.lockInterruptibly();
        try {
            // 如果队列里没数据了,就进入等待队列
            while (count.get() == 0) {
                notEmpty.await();
            }
            // 出队
            x = dequeue();
            c = count.getAndDecrement();
            // 还有数据,试着唤醒别的等待队列里的消费者
            if (c > 1)
                notEmpty.signal();
        } finally {
            // 解锁
            takeLock.unlock();
        }
        // 本来是满的,走到这说明消费了一个,获取 put 锁,然后唤醒一个 put 线程进行生产
        if (c == capacity)
            signalNotFull();
        return x;
    }

PriorityBlockingQueue

一个支持优先级的无界队列,不是先进先出的顺序,因为是无界队列,所以不需要 put 锁(存数据的锁),只需要在队列没数据的时候阻塞住读的线程即可。

SynchronousQueue

  • 队列长度为0,因为他不需要存储起来,有一个 put 就必须有另外一个调用 take 方法,用 LockSuport 进行线程通信。
  • 因为没有头节点所以不需要 peek 方法。
  • 用来直接传递并发数据效率很快。

DelayQueue

无界阻塞队列,基于 PriorityQueue 实现,根据延时时间长短进行排序,也是使用 Condition 进行线程通信

  • 如果队里有数据使用 awaitNanos(指定时间),等待指定时间。
  • 没数据直接 await,等待 put 后唤醒。