Java集合框架中的Queue与LinkedBlockingQueue使用

Queue接口不能直接实例化,必须使用LinkedList、PriorityQueue或LinkedBlockingQueue等实现类;LinkedBlockingQueue的capacity参数影响阻塞行为与内存安全;poll()返回null而take()会阻塞;size()在高并发下不可靠,不宜用作条件判断。

Queue接口不能直接new,必须用实现类

Java里Queue是接口,不是具体类,写new Queue()会编译报错。常见误操作是想快速测试队列行为,却卡在实例化这步。实际要用它的实现类,比如LinkedList(非线程安全)、PriorityQueue(带排序)、或者LinkedBlockingQueue(线程安全、阻塞式)。

选择依据主要看场景:

  • 单线程、简单FIFO:用LinkedListArrayDeque(后者性能更好)
  • 多线程、需要等待入队/出队:必须用LinkedBlockingQueue这类阻塞队列
  • 需要按优先级取元素:选PriorityQueue,但注意它不保证线程安全

LinkedBlockingQueue的capacity参数不是可有可无

LinkedBlockingQueue构造时可以传一个int capacity,比如new LinkedBlockingQueue(10)。这个值决定了队列最大长度,一旦满,后续put()会阻塞;如果没设(用无参构造),默认容量是Integer.MAX_VALUE,看起来“无限”,但实际可能引发OOM——特别是生产者远快于消费者时,任务持续堆积。

典型踩坑场景:

  • 用无参构造做消息缓冲,系统负载升高后内存暴涨
  • 设了capacity但没配好监控,队列满后put()线程一直挂起,下游处理延迟飙升
  • offer()混用:它不会阻塞,返回false,但业务代码没判断返回值,导致任务静默丢失

poll() vs take():空队列时的行为差异直接影响线程控制

LinkedBlockingQueue取元素,poll()take()看着相似,但空队列时表现完全不同:

Queue q = new LinkedBlockingQueue<>();
q.poll();   // 立即返回 null
q.take();   // 无限期阻塞,直到有元素或被中断

这个差异决定你是否要自己写轮询逻辑:

  • poll()就得加while循环+Thread.sleep(),容易写成忙等或响应慢
  • take()更简洁,但必须处理InterruptedException,且线程被中断时会抛异常,不捕获会导致线程意外退出
  • 生产环境推荐take() + 正确的中断处理,避免资源泄漏

LinkedBlockingQueue的size()在高并发下不准,别用它做条件判断

LinkedBlockingQueue.size()返回的是当前元素个数,但它内部用的是AtomicInteger计数,看似线程安全。问题在于:这个值只是调用瞬间的快照,之后立刻可能被其他线程修改。拿它做判断极易出错,比如:

if (queue.size() < 10) {
    queue.put(item); // 这里可能因并发导致实际超限
}

更糟的是,size()在某些实现里(如ConcurrentLinkedQueue)甚至不保证精确,只保证“估计值”。对LinkedBlockingQueue来说,真正可靠的边界控制只能靠put()阻塞机制本身,或者用offer(E, timeout, unit)配合超时重试。

真正需要感知队列水位时,建议用JMX暴露remainingCapacity(),而不是在业务逻辑里反复查size()