Skip to content

Java线程池配置误区

核心线程数与最大线程数设置不合理

  • 误区:将核心线程数(corePoolSize)和最大线程数(maximumPoolSize)设置得过高或过低。
  • 原因
    • 如果将核心线程数设置得太高,线程池可能会保持大量线程,即使在负载较低时也不会释放这些线程,浪费资源。
    • 如果将最大线程数设置得过高,线程池可能会同时创建太多线程,造成系统负担过重。
  • 解决方案
    • 核心线程数应与系统负载和任务量相关,通常对于 CPU 密集型任务,核心线程数可以设置为系统的 CPU 核心数;对于 I/O 密集型任务,可以稍微增加核心线程数。
    • 最大线程数应基于业务负载和系统资源来合理配置。
java
int corePoolSize = Runtime.getRuntime().availableProcessors();
int maximumPoolSize = corePoolSize * 2;

队列选择不当

  • 误区:选择了不合适的队列类型,尤其是在高并发或高负载的场景下。
  • 原因
    • 使用 SynchronousQueue,这会导致每个任务必须直接提交给工作线程,若没有空闲线程,任务会被拒绝,而没有缓冲队列存放任务。
    • 使用无限制的 LinkedBlockingQueue 可能会导致线程池无限增长,导致内存溢出或 CPU 占用过高。
  • 解决方案
    • 对于高并发场景,可以使用 ArrayBlockingQueue 或 LinkedBlockingQueue(有界队列)来避免过多线程的创建。
    • 对于吞吐量较大的应用,使用 SynchronousQueue 可以避免过度积压,但要小心使用,确保最大线程数的合理配置。
java
ExecutorService executor = new ThreadPoolExecutor(
    corePoolSize, maximumPoolSize,
    60L, TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(100)  // 有界队列
);

拒绝策略

拒绝策略适用场景缺点
CallerRunsPolicy确保任务不会丢失,负载较轻的场景,但可能影响业务线程性能。如果线程池已经达到其最大容量,并且工作队列已满,无法接受新的任务,那么新提交的任务将不会异步执行,而是会直接在提交任务的调用者线程(即主线程或调用线程池的线程)中同步执行。
AbortPolicy必须明确告知任务无法执行的场景,适合容忍任务丢失的业务。直接抛出 RejectedExecutionException 异常。
DiscardPolicy用于任务丢失是可以接受的场景,比如任务执行的顺序不重要,或者任务的结果不影响业务逻辑的情况下。直接丢弃任务,不执行也不抛出异常。
DiscardOldestPolicy同上, 不过更注重时效丢弃队列中最老的任务,然后尝试再次提交当前任务。

线程池不适当的空闲线程回收设置

  • 误区:没有设置合理的空闲线程回收时间,或者回收时间过长。
  • 原因
    • 线程池的空闲线程如果长时间没有执行任务,依然占用系统资源,这会造成资源浪费。keepAliveTime 是控制空闲线程存活时间的参数,设置过短可能会频繁创建和销毁线程,设置过长则会浪费资源。
  • 解决方案
    • 设置适当的 keepAliveTime,例如 60 秒是一个常见的选择,这样空闲线程在不活跃时能够快速销毁,释放资源。
java
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    corePoolSize, maximumPoolSize,
    60L, TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(100)
);

线程池与定时任务的混淆

  • 误区:使用普通的线程池处理定时任务,导致定时任务执行不稳定。
  • 原因
    • 线程池并不是专门设计用于定时任务,它会按照任务到达的顺序立即执行任务,无法保证任务按照预定的时间间隔执行。普通线程池不具备调度功能,使用它来执行定时任务会造成不准确的调度。
  • 解决方案
    • 使用 ScheduledExecutorService 来处理定时任务,它专门设计用于执行延迟或周期性任务,并且有更好的任务调度机制。
java
ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(1);
scheduledExecutor.scheduleAtFixedRate(() -> {
    // 执行定时任务
}, 0, 1, TimeUnit.SECONDS);  // 每1秒执行一次

无法区分不同任务的线程

  • 误区:使用默认的线程工厂,导致线程命名不规范,无法区分不同任务的线程。
  • 原因
    • Java 默认的线程工厂会为线程池中的线程设置默认的线程名称,而线程名称的命名规范有助于调试和日志管理。
  • 解决方案
    • 使用自定义线程工厂来为每个线程命名,并根据需要设置线程的优先级、是否守护线程等。
java
ThreadFactory factory = new ThreadFactoryBuilder()
    .setNameFormat("my-thread-pool-%d")
    .setDaemon(true)
    .build();
ExecutorService executor = new ThreadPoolExecutor(
    corePoolSize, maximumPoolSize, 60L, TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(100), factory
);

粤ICP备20009776号