前言

并发工具类、队列和停机策略共同决定异步任务在压力下是否可控。本文对比常见协作工具与生产消费模型,说明如何通过有界队列、背压、任务取消和优雅停机,把无限积压和粗暴中断转化为可预期的业务行为。

并发工具类怎么选

工具 核心语义 可否复用 生产场景
CountDownLatch 等 N 件事完成再继续 并行加载多个启动配置、等待批任务结束
CyclicBarrier N 个参与者到齐后一起继续 分阶段并行计算、模拟并发压测
Semaphore 同时最多允许 N 个许可 限制外部 API、文件处理、昂贵资源并发数
Phaser 可动态注册参与者的多阶段屏障 参与者数量动态的分阶段任务
BlockingQueue 生产者和消费者之间的有界缓冲 异步批处理、日志/任务削峰
DelayQueue 到期才可取出的队列 单机延迟任务、超时清理

CountDownLatch 适合“主线程等子任务”;CyclicBarrier 适合“子任务互相等”;Semaphore 不是锁,它限制的是并发许可证数量。

有界队列和背压

生产速度大于消费速度时,队列长度会持续增长。无界队列只是在延后失败:内存越来越高、延迟越来越大,最后 OOM。正确做法是让队列有界,并定义满时的业务语义。

flowchart LR
    P[生产者] --> Q{有界 BlockingQueue}
    Q --> C[消费者]
    Q -->|已满| B[阻塞、超时、拒绝、降级或落可靠 MQ]
队列满后的选择 适合情况 风险
阻塞 put 上游可以被反压,任务不可丢 占满请求线程会导致级联阻塞
超时 offer 允许快速失败或转备用通道 必须记录失败并通知调用方
丢弃 指标、可重建日志等 关键业务会数据丢失
转 MQ/持久化任务表 关键异步任务 需要幂等、重试和可观测性

批处理消费

数据库批量写入、文件导入常用“积累到数量或等待到时间就刷一次”的模式。批次不能只追求大:批次过大占内存、锁和事务时间过长;过小则网络和提交开销高。应从数据库连接、SQL 耗时、失败重试粒度和压测结果确定。

消费者处理失败时要区分可重试和不可重试:网络超时可有限重试;格式非法应记录错误并转人工/死信,不要无限重试把队列堵死。

延迟任务与定时任务边界

DelayQueue 在单 JVM 内适合超时关闭、短延迟重试等可丢失或可重建任务。服务重启后队列内数据会消失,集群也会有重复消费/分配问题。订单超时取消、对账补偿等可靠任务应放到数据库任务表、Redis 延迟结构、MQ 延迟消息或分布式调度系统,并用状态机和幂等保证正确性。

优雅停机的消费顺序

  1. 从负载均衡摘除实例,停止接收新请求。
  2. 停止接收新任务,关闭生产入口。
  3. 给消费者限定时间处理在途任务并提交结果。
  4. 对未完成任务记录进度,依赖幂等在下一实例重试。
  5. 关闭线程池、连接和客户端资源。

不要以“线程被中断”作为业务完成标志。中断是协作信号,任务代码应检查中断、释放资源,并把可恢复状态持久化。

高频面试题

问题 回答要点
Latch 和 Barrier 区别? Latch 是一次性倒计时等待;Barrier 是多方到齐后继续且可复用。
Semaphore 能解决限流吗? 能限制本 JVM 并发量;分布式全局限流需 Redis/网关等共享方案。
为什么队列必须有界? 把过载转化为可见的反压/拒绝,避免无限延迟和 OOM。
DelayQueue 能做订单超时吗? 单机、重启丢任务,不能单独承担可靠分布式订单超时。