java并发编程

java 线程池工作原理及参数设置

2025-02-10  本文已影响0人  饱饱抓住了灵感

参考你知道怎么合理设置线程池参数吗?

一、ThreadPoolExecutor

在 Java 中,ThreadPoolExecutor 是线程池的核心实现类。它提供了多个构造方法,可以灵活地配置线程池的参数。
接下来我们就来详解看看线程池的参数,源码定义如下:

    /**
     * Creates a new {@code ThreadPoolExecutor} with the given initial
     * parameters.
     *
     * @param corePoolSize the number of threads to keep in the pool, even
     *        if they are idle, unless {@code allowCoreThreadTimeOut} is set
     * @param maximumPoolSize the maximum number of threads to allow in the
     *        pool
     * @param keepAliveTime when the number of threads is greater than
     *        the core, this is the maximum time that excess idle threads
     *        will wait for new tasks before terminating.
     * @param unit the time unit for the {@code keepAliveTime} argument
     * @param workQueue the queue to use for holding tasks before they are
     *        executed.  This queue will hold only the {@code Runnable}
     *        tasks submitted by the {@code execute} method.
     * @param threadFactory the factory to use when the executor
     *        creates a new thread
     * @param handler the handler to use when execution is blocked
     *        because the thread bounds and queue capacities are reached
     * @throws IllegalArgumentException if one of the following holds:<br>
     *         {@code corePoolSize < 0}<br>
     *         {@code keepAliveTime < 0}<br>
     *         {@code maximumPoolSize <= 0}<br>
     *         {@code maximumPoolSize < corePoolSize}
     * @throws NullPointerException if {@code workQueue}
     *         or {@code threadFactory} or {@code handler} is null
     */
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

这里特意把注释也复制了一下,因为写的非常清楚明了,我们结合注释和源码浅析来看看每一个参数的含义所在:

二、线程池任务执行流程

执行任务是线程池ThreadPoolExecutor的主要入口,当我们提交了一个任务之后,线程池将通过如下流程执行任务:

线程池工作原理.jpeg
  1. 线程池会判断当前运行线程数是否小于核心线程数。如果是,则立即创建一个新的工作线程来执行任务。如果不是则进入下个流程。
  2. 线程池判断任务队列是否已经满。如果任务队列没有满,则将新提交的任务放入这个任务队列中,等待工作线程执行。如果满了,则进入下个流程。
  3. 线程池判断当前运行的线程数是否小于最大线程数。如果是,则创建一个新的工作线程来执行任务。如果不是,则进入下个流程
  4. 最后交给拒绝策略RejectedExecutionHandler来处理这个任务,拒绝策略如下:

三、为啥不建议使用Executors创建线程池

内置工具类Executors 提供的线程池(如 newFixedThreadPoolnewCachedThreadPoolnewSingleThreadExecutor)使用了一些默认配置(如最大线程数、队列类型等),这就可能任务积压,线程无限创建从而导致内存资源耗尽产生OOM。阿里巴巴Java开发手册强调了严禁使用Executors创建线程池。

FixedThreadPoolSingleThreadExecutor:使用的是有界阻塞队列是 LinkedBlockingQueue ,其任务队列的最大长度为 Integer.MAX_VALUE ,可能堆积大量的请求,从而导致 OOM。

CachedThreadPool:使用的是同步队列 SynchronousQueue, 允许创建的线程数量为 Integer.MAX_VALUE ,如果任务数量过多且执行速度较慢,可能会创建大量的线程,从而导致 OOM。

ScheduledThreadPoolSingleThreadScheduledExecutor :使用的无界的延迟阻塞队列 DelayedWorkQueue ,任务队列最大长度为 Integer.MAX_VALUE ,可能堆积大量的请求,从而导致 OOM。

四、线程池参数大小设置多少才合理

Java线程池的核心数设置应根据实际应用场景、服务器硬件配置以及工作负载的特性进行合理配置。合理设置线程池的核心数可以提升应用程序的性能、减少资源浪费,同时防止系统因线程过多而出现性能瓶颈。

线程池核心数的合理设置取决于任务的类型,即任务是 CPU密集型 还是 IO密集型

4.1 CPU密集型任务

CPU密集型任务是指需要大量计算资源的任务,如数学计算、数据处理、加密等。这类任务主要消耗CPU资源,通常不需要等待外部资源(如网络、磁盘I/O等)。

4.2 IO密集型任务

IO密集型任务是指需要频繁等待外部资源的任务,如数据库访问、文件读写、网络请求等。这类任务在运行过程中,线程大部分时间处于等待状态,因此CPU负载不高。

在Java中可以通过以下方式获取系统的CPU核心数:

int cpuCores = Runtime.getRuntime().availableProcessors();

注意:当今服务器硬件资源配置有1个cpu核心2个线程的说法,比如说“4核8线程 8核16线程…”,这是不是意味着我们cpu密集型的线程池核心线程数大小应该设置为2*cpuCores + 1了呢?其实不然

4核8线程 的 CPU 上,Java 线程池核心数的设置取决于任务的性质(CPU密集型 vs IO密集型)以及 CPU 资源的实际利用。推荐设置为 4 + 1 = 5 而不是 8 + 1 = 9 是因为 CPU密集型任务 最有效利用的是 物理核心,而不是逻辑线程。下面详细说明原因。

4.3 物理核心 vs 逻辑线程

物理核心数决定了 CPU 密集型任务的效率:在 CPU 密集型任务中,线程的执行时间完全依赖 CPU 的运算能力。一个物理核心同时只能执行一个计算密集型线程。如果超出了物理核心的数量,多个线程会开始竞争 CPU 资源,导致上下文切换,增加了不必要的开销和延迟。因此,4 个物理核心 正好可以处理 4 个计算密集型任务,推荐加 1 是为了应对偶尔的任务阻塞或线程等待情况,这样即使一个线程在等待,CPU 仍然能继续执行其他线程的计算任务。

超线程的作用超线程技术 主要用于提高 CPU 利用率,在某些线程发生等待(例如 I/O 等待)时,让物理核心在其他空闲时间执行另一个线程的任务。然而,对于 CPU密集型 任务,由于每个线程始终需要完全占用物理核心进行计算,超线程的作用有限。超线程的两个逻辑线程并不会提高 CPU 计算能力,只是提高了任务调度的并行度。在 CPU 密集型场景下,超线程不会显著提升性能,过多线程反而可能导致性能下降

上一篇 下一篇

猜你喜欢

热点阅读