1. 线程池的基本概念

线程池是用来管理线程的工具,它可以有效地控制并发任务的执行。通过线程池,可以:

  • 降低创建和销毁线程的开销。
  • 限制同时运行的线程数,避免过多线程导致系统资源过载。
  • 提供线程复用,减少上下文切换的开销。

2. 线程池的核心参数

  • 核心线程数 (corePoolSize):线程池中始终保持活动的线程数。如果任务队列中有任务,线程池会首先使用这些核心线程来执行任务。
  • 最大线程数 (maximumPoolSize):线程池中允许的最大线程数。当线程池中的线程数达到核心线程数时,若队列满了,线程池将创建新的线程直到最大线程数。
  • 空闲线程存活时间 (keepAliveTime):当线程池中的线程数大于核心线程数时,空闲线程的最大存活时间。
  • 任务队列 (workQueue):用来保存等待执行任务的队列。当线程池中所有核心线程都在执行任务时,新任务会被添加到任务队列中。
  • 拒绝策略 (RejectedExecutionHandler):当线程池无法处理新的任务时的处理策略。

3. 根据任务类型设置线程池参数

CPU 密集型任务的线程池配置

  • CPU 密集型任务:这些任务需要大量的计算,不会有明显的 IO 等待时间。过多的线程会导致线程之间竞争 CPU 资源,增加上下文切换的开销,从而降低系统性能。

推荐配置

  • 核心线程数CPU 核数,让每个核心都有一个线程。
  • 最大线程数CPU 核数,不需要超过 CPU 核数。
  • 队列类型:通常选择 LinkedBlockingQueue(无界队列)或 ArrayBlockingQueue(有界队列)。
  • 任务数较少:可考虑设置较小的队列长度,避免内存占用过多。

示例

ExecutorService executor = new ThreadPoolExecutor(
    2, // 核心线程数(例如:2 核 CPU)
    2, // 最大线程数(例如:2 核 CPU)
    0L, TimeUnit.MILLISECONDS,  // 空闲线程存活时间
    new LinkedBlockingQueue<>(100) // 队列大小
);

IO 密集型任务的线程池配置

  • IO 密集型任务:这些任务大部分时间处于等待外部资源(如文件 I/O、网络 I/O 等)期间,因此它们不占用大量 CPU 资源,可以使用更多的线程来提高吞吐量。

推荐配置

  • 核心线程数CPU 核数,通常设置为 CPU 核数。
  • 最大线程数CPU 核数 / (1 - 阻塞系数),根据任务的阻塞系数估算线程数。例如,阻塞系数为 0.7 时,可以设置最大线程数为 2 / (1 - 0.7) = 6
  • 任务队列:通常使用 LinkedBlockingQueueArrayBlockingQueue,根据内存和任务量的实际情况来调整队列大小。

示例

int cpuCoreCount = 2;
double blockFactor = 0.7; // 阻塞系数
int maxThreads = (int) Math.ceil(cpuCoreCount / (1 - blockFactor)); // 计算最大线程数

ExecutorService executorService = new ThreadPoolExecutor(
    cpuCoreCount,           // 核心线程数
    maxThreads,            // 最大线程数
    0L, TimeUnit.MILLISECONDS,  // 空闲线程存活时间
    new LinkedBlockingQueue<>(100) // 任务队列
);

4. 线程池的空闲线程存活时间

  • 空闲线程存活时间:对于短生命周期的任务,可以将线程的空闲存活时间设置为 0,以便线程池在任务执行完毕后立即销毁空闲线程。而对于长期运行的服务,可以适当增加线程的空闲存活时间,避免频繁的线程创建和销毁。

示例

new ThreadPoolExecutor(
    2, // 核心线程数
    6, // 最大线程数
    60L, TimeUnit.SECONDS, // 空闲线程存活时间
    new LinkedBlockingQueue<>(100) // 任务队列
);

5. 线程池的拒绝策略

当线程池无法处理新的任务时,会触发 拒绝策略。常见的拒绝策略有:

  • AbortPolicy(默认策略):抛出 RejectedExecutionException 异常。
  • CallerRunsPolicy:由调用者线程执行任务,避免任务丢失。
  • DiscardPolicy:直接丢弃任务。
  • DiscardOldestPolicy:丢弃最旧的任务。

示例

new ThreadPoolExecutor(
    2, 6, 60L, TimeUnit.SECONDS, 
    new LinkedBlockingQueue<>(100),
    new ThreadPoolExecutor.CallerRunsPolicy()  // 使用调用者执行策略
);

6. 总结

  • 对于 CPU 密集型任务,线程数通常设置为 CPU 核数,最大线程数不超过 CPU 核数,避免过多线程引发上下文切换的性能损失。
  • 对于 IO 密集型任务,由于任务在等待 IO 时不会占用 CPU,可以适当增加线程数。最大线程数的设置可以参考公式:$$ \text{最大线程数} = \frac{\text{CPU 核数}}{1 - \text{阻塞系数}} $$。
  • 合理的线程池参数配置能显著提高系统的性能,避免线程过多带来的资源浪费和上下文切换开销。

今夜有点儿凉
40 声望3 粉丝

今夜有点儿凉,乌云遮住了月亮。