线程池详解

自定义线程池

线程池(ThreadPool)在Java中是通过Executor框架实现的,它允许你以池化的方式管理线程,复用线程并控制最大并发数,从而提高资源的利用率和系统的稳定性。ThreadPoolExecutorExecutor接口的一个实现,它有7个核心参数和4种拒绝策略。

7大参数

  1. corePoolSize
    • 核心线程数,即使它们是空闲的,线程池也会保持存活的线程数量。
  2. maximumPoolSize
    • 线程池能够容纳同时执行的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。
  3. keepAliveTime
    • 当线程数大于核心线程数时,这是多余空闲线程在终止前等待新任务的最长时间。
  4. unit
    • keepAliveTime的时间单位。
  5. workQueue
    • 用于保存等待执行的任务的阻塞队列。常用的队列有如下几种:
      • ArrayBlockingQueue:基于数组结构的有界阻塞队列,按 FIFO 排序任务。
      • LinkedBlockingQueue:基于链表结构的阻塞队列,吞吐量通常要高于ArrayBlockingQueue
      • SynchronousQueue:一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,反之亦然。
      • PriorityBlockingQueue:具有优先级的无界阻塞队列。
  6. threadFactory
    • 用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程设置更有意义的名字。
  7. handler
    • 拒绝策略,当任务太多来不及处理,如何拒绝任务。

4大拒绝策略

  1. ThreadPoolExecutor.AbortPolicy
    • 抛出RejectedExecutionException异常来拒绝新任务的处理。
  2. ThreadPoolExecutor.CallerRunsPolicy
    • 调用任务的run()方法绕过线程池直接执行。
  3. ThreadPoolExecutor.DiscardPolicy
    • 不处理新任务,直接丢弃掉。
  4. ThreadPoolExecutor.DiscardOldestPolicy
    • 丢弃队列中最老的一个任务,并尝试再次提交当前任务。

实例代码

import java.util.concurrent.*;

public class ThreadPoolDemo {
    public static void main(String[] args) {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
            5, // corePoolSize
            10, // maximumPoolSize
            60L, // keepAliveTime
            TimeUnit.SECONDS, // unit
            new LinkedBlockingQueue<>(100), // workQueue
            Executors.defaultThreadFactory(), // threadFactory
            new ThreadPoolExecutor.AbortPolicy() // handler
        );

        // 提交任务
        executor.execute(() -> {
            // 任务代码
        });

        // 关闭线程池
        executor.shutdown();
    }
}

核心线程数详解

线程池的线程核心数(core pool size)是指线程池中保持活跃的线程数量,即使它们处于空闲状态。这个数值是在创建线程池时设置的,并且可以根据具体的应用需求进行调整。线程池的核心数与CPU的内核数(core count)有一定的关系,但它们并不是直接对应的。

CPU的内核数是指物理CPU中的核心数量。一个CPU核心可以独立执行计算任务,而多核心CPU可以同时执行多个计算任务,从而提高并行处理能力。在设计并发程序时,理解CPU的内核数对于优化线程池的大小是很重要的。

以下是线程池线程核心数与CPU内核数关系的一些考虑因素:

  1. CPU密集型任务:
    如果你的应用程序主要执行CPU密集型任务(如复杂的计算),那么线程池的大小通常设置为CPU内核数的数量或稍微多一点。这是因为CPU密集型任务会持续使用CPU资源,而增加更多的线程并不会提高性能,因为CPU核心已经饱和。
  2. IO密集型任务:
    如果你的应用程序主要执行IO密集型任务(如文件操作、网络通信等),那么线程池的大小可以设置得比CPU内核数多得多。这是因为IO密集型任务经常会因为等待IO操作而阻塞,这时CPU核心不会被充分利用。增加更多的线程可以确保CPU在等待IO时仍然有任务可以执行,从而提高程序的吞吐量。
  3. 混合型任务:
    对于同时包含CPU密集型和IO密集型任务的应用程序,线程池的大小可能需要根据任务的具体特性和比例来调整。
  4. 上下文切换开销:
    创建过多的线程可能会导致频繁的上下文切换,这会增加额外的开销,从而降低程序的整体性能。因此,即使是IO密集型任务,也应避免设置过高的线程池大小。

在实践中,确定线程池的最佳大小通常需要基于应用程序的性能测试和调优。有时候,开发者会使用经验公式来估算线程池的大小,例如:

线程池大小 = CPU内核数 * (1 + 平均等待时间 / 平均工作时间)

这个公式考虑了任务的等待时间(如IO操作)和实际工作时间(CPU计算时间)的比例。然而,这只是一个起点,最终的线程池大小应该根据实际的应用程序负载和性能指标来确定。

总结

在实际应用中,应该根据任务的性质和系统资源合理配置线程池的参数,以避免资源浪费或过载。例如,CPU 密集型任务应该配置较小的线程池,通常接近CPU核心数,而IO密集型任务由于线程并不是一直在执行任务,可以配置更多的线程。

你可能感兴趣的:(java,网络协议,开发语言)