drainto 是 arrayblockingqueue 中最高效的批量取元素方式,它原子性转移内部数组数据,避免锁竞争和对象创建;需复用目标集合、结合 poll 做兜底、释放锁后再处理数据。

drainTo 是 ArrayBlockingQueue 中最高效的批量取元素方式,它直接转移内部数组数据,避免逐个出队的锁竞争和对象创建开销。
drainTo 的核心用法与注意事项
调用 drainTo(Collection c) 或 drainTo(Collection c, int maxElements) 会原子性地将队列中所有(或最多指定数量)元素转移到目标集合,并清空对应位置。关键点:
- 目标集合不能是队列本身或其包装视图(否则抛
IllegalArgumentException) - 不保证元素顺序严格按 FIFO(底层是循环数组拷贝,但对用户逻辑无影响)
- 返回实际转移的元素个数,为 0 表示队列为空
- 不会触发任何元素的
toString()或额外封装,纯引用搬运
高效使用的三个实践要点
真正提升性能的关键不在调用本身,而在上下文配合:
- 复用目标集合:每次新建 ArrayList 会触发扩容和内存分配。建议使用 `new ArrayList(queue.size())` 预设容量,或更优——维护一个可重用的 List 实例(注意线程安全,如单线程消费场景下可复用)
-
结合 poll() 做兜底处理:drainTo 不阻塞,若需“等待并取一批”,应先用
poll(timeout)确保有数据,再 drainTo;避免空轮询 -
避免在锁内做耗时操作:drainTo 内部持有队列锁,若后续立即遍历处理大量元素,应先把 list 赋值、释放锁,再处理。例如:
Listbatch = new ArrayList(128);
int n = queue.drainTo(batch);
// 锁已释放,现在 safe 处理 batch batch.forEach(Task::execute);
对比其他批量获取方式
比起手动循环 poll() 或流式 stream().forEach(),drainTo 明显更快:
- 单次加锁 + 数组块拷贝,O(n) 时间但常数极小
- 手动 poll 需要 n 次锁进出 + n 次 CAS 或 volatile 读,还可能因空队列自旋浪费 CPU
- stream() 会触发迭代器创建、Spliterator 分割等额外对象分配,且无法规避锁粒度问题
典型高吞吐消费模式
适合日志收集、消息批处理、定时批量落库等场景:
- 固定周期(如每 100ms)调用 drainTo,控制批大小(如 maxElements=1000),防止单批过大导致处理延迟
- 搭配 LinkedBlockingQueue 时慎用 drainTo(它不保证原子性批量转移),ArrayBlockingQueue 才真正支持高效 drainTo
- 若下游处理慢,可配合背压策略:根据 drainTo 返回数量动态调整下次拉取间隔,而非固定频率
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











