队列经常用来连接生产数据和处理数据的线程。选择队列之前,先想清楚两件事:没有数据时要不要等待,数据积压时允许占用多少空间。

非阻塞队列

ConcurrentLinkedQueue 是线程安全的无界队列,常用操作是 offer() 和 poll()。没有元素时,poll() 返回 null,不会等生产者放入数据。

JDK 8 的实现以链表和 CAS 为基础,入队、出队通过更新节点链接和值完成,不依靠一把锁把所有访问串起来。它适合需要立即返回的队列操作,但如果消费者在空队列上无限循环 poll(),仍然会消耗 CPU。

不要先判断 isEmpty() 再认为 poll() 一定有值。判断之后,其他消费者可能已经取走元素,直接检查 poll() 的返回值即可。

BlockingQueue 的几组方法

操作抛异常返回特殊值一直等待限时等待
插入addofferputoffer(value, time, unit)
取出removepolltakepoll(time, unit)
查看队头elementpeek——

这里的“等待”主要指满或空时的行为。put() 和 take() 可以响应中断,使用时需要处理 InterruptedException。队列不接受 null,这也使 poll() 返回 null 的含义明确。

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class QueueDemo {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(1);
        System.out.println(queue.offer(10));
        System.out.println(queue.offer(20));
        System.out.println(queue.take());
        System.out.println(queue.poll());
    }
}

输出 true、false、10、null。容量为 1 时,第二次 offer 失败,程序可以决定拒绝、稍后重试或交给其他处理方式。

几种实现

ArrayBlockingQueue 使用固定大小的数组,创建时确定容量。JDK 8 实现用一把锁配合非空、未满两个条件。

LinkedBlockingQueue 使用链表,可以指定容量;不指定时上限是 Integer.MAX_VALUE,实际使用仍受内存限制。它分别协调插入和取出,并用原子计数连接两侧的状态,不意味着完全没有锁。

SynchronousQueue 不保存元素,每次交付都需要生产者和消费者配对。它的容量不是“特别小”,而是没有用于积压任务的内部存储。

PriorityBlockingQueue 按优先级出队,逻辑上无界;相同优先级不保证 FIFO。DelayQueue 只允许取出延迟已经到期的元素,空或队头尚未到期时 take 会等待。

需要双端操作可考虑 LinkedBlockingDeque。LinkedTransferQueue.transfer() 则能等待元素被消费者接收;这和普通 put() 只保证元素成功进入队列不同。

容量也是控制手段

有界队列能把积压限制在一定范围内,但它不会自动决定满了怎么办。选择等待还是拒绝,要结合请求能等待多久、任务是否可以丢弃。

还要注意,把对象放进队列后又在外面修改它,可能与消费者发生数据竞争。队列安全地交付了引用,并不替你保护交付之后的所有修改。

最后修改:2018 年 03 月 12 日
如果觉得我的文章对你有用,请随意赞赏