Java并发编程中的任务队列与线程池使用

优先选 ArrayBlockingQueue,因其有界性可暴露容量瓶颈并配合拒绝策略保障稳定;若用 LinkedBlockingQueue 必须显式指定容量,禁用无界构造。

线程池创建时用 LinkedBlockingQueue 还是 ArrayBlockingQueue

取决于任务提交频率和可控性。LinkedBlockingQueue 默认无界,看似安全,但容易掩盖背压问题——当生产者(提交任务)远快于消费者(线程执行),队列无限增长,最终触发 OutOfMemoryErrorArrayBlockingQueue 是有界队列,能强制暴露容量瓶颈,配合合理的拒绝策略更利于系统稳定。

  • 高吞吐、任务执行时间短且可预测 → 选 ArrayBlockingQueue,设合理容量(如核心线程数 × 2~4)
  • 任务来源不可控、需容忍短时突发 → 可用 LinkedBlockingQueue,但必须显式指定容量(如 new LinkedBlockingQueue(1000)),绝不用无参构造
  • 若用 SynchronousQueue,它不存储任务,仅作“手递手”中转,适合 CPU 密集型、希望立即触发线程创建的场景,但会频繁启停线程,慎用于 I/O 密集型

ThreadPoolExecutorcorePoolSizemaximumPoolSize 怎么设?

这不是调优起点,而是业务负载建模的结果。盲目设大值会导致线程争抢 CPU 或阻塞在 I/O 上,反而降低吞吐;设太小则任务积压、响应延迟升高。

  • CPU 密集型任务:理论上限 ≈ Runtime.getRuntime().availableProcessors()corePoolSizemaximumPoolSize 可设为相同值,避免线程上下文切换开销
  • I/O 密集型任务(如 HTTP 调用、DB 查询):需估算平均 I/O 等待时间占比,公式参考:corePoolSize ≈ CPU 核数 / (1 - 阻塞系数),其中阻塞系数常取 0.8~0.9,即单线程约 80% 时间在等 I/O
  • 永远不要把 maximumPoolSize 设为 Integer.MAX_VALUE —— 这会让线程池在队列满后无限制新建线程,极大概率 OOM

为什么 submit(Runnable) 返回的 Future 调用 get() 会阻塞?

因为 Future.get() 是同步等待语义,它会挂起当前

线程直到任务完成或超时。这不是 bug,而是设计使然。但在线程池内部调用它,极易造成死锁——比如所有工作线程都在等另一个任务的 get(),而那个任务又排在队列里,无人执行。

  • 避免在 RunnableCallable 内部调用同一线程池中其他任务的 future.get()
  • 如需组合多个异步结果,优先用 CompletableFuture.supplyAsync(..., executor) 配合 thenCombine/allOf,它们不阻塞线程,而是注册回调
  • 若必须阻塞等待,确保调用方不在该线程池的工作线程中(例如放在 Servlet 线程或独立的调度线程里)

关闭线程池时,shutdown()shutdownNow() 的实际行为差异

shutdown() 是温和终止:不再接受新任务,但会等所有已提交任务(包括队列中未执行的)完成;shutdownNow() 是强制中断:尝试停止正在执行的任务(通过 Thread.interrupt()),并清空队列、返回未执行任务列表。

  • 绝大多数场景应优先用 shutdown() + awaitTermination(long, TimeUnit),给任务留出合理收尾时间(如释放连接、刷盘日志)
  • shutdownNow() 仅适用于可中断任务(即代码中检查了 Thread.currentThread().isInterrupted() 并主动退出),否则中断信号被忽略,任务仍会跑完
  • Spring 中用 @PreDestroy 关闭线程池时,务必加超时等待逻辑,否则应用可能假死在关闭阶段
executor.shutdown();
try {
    if (!executor.awaitTermination(30, TimeUnit.SECONDS)) {
        executor.shutdownNow();
        if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
            System.err.println("线程池未正常关闭");
        }
    }
} catch (InterruptedException e) {
    executor.shutdownNow();
    Thread.currentThread().interrupt();
}

真正难的不是选哪个队列或设几个线程,而是理解每个参数背后对资源竞争、任务生命周期和错误传播路径的影响。多数线上问题,都出在“以为设了线程池就等于并发安全”,却没管住任务本身的阻塞点和依赖关系。