关于ThreadPoolExecutor

本文详细解析了ThreadPoolExecutor的工作原理,包括构造方法参数详解、线程池生命周期管理、任务执行流程及扩展钩子方法。同时提供了CPU密集型与IO密集型任务的线程池配置建议。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

基本使用:使用 new ThreadPoolExecutor创建线程池

一般使用new ThreadPoolExecutor方式,Executors的几个工厂方法实现也是 new ThreadPoolExecutor的方式,不过是参数值不一样。
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;
    }
  1. corePoolSize - 核心线程数。
  2. maximumPoolSize - 池中允许的最大线程数。核心 + 非核心
  3. keepAliveTime - 当线程数大于核心线程数时,为空闲线程闲置时间。如果设置allowCoreThreadTimeOut = true,核心线程也会受此值影响。
  4. unit - keepAliveTime 参数的时间单位。
  5. workQueue - 执行前用于保持任务的队列。此队列仅保持由 execute 方法提交的Runnable 任务。
    常用的workQueue类型:SynchronousQueue、LinkedBlockingQueue、ArrayBlockingQueue、DelayQueue。
    • 直接传递的队列:SynchronousQueue,不会持有任务,通常没有可用线程就创建一个。
    • 无界队列:没有设置容量的LinkedBlockingQueue,使用与每个任务相对独立,比如网页服务器。
    • 有界队列:例如ArrayBlockingQueue,可以节省资源。小队列大池,大队列小池,需要自己控制调优。
  6. 线程创建工厂:默认使用Executors.defaultThreadFactory(),设置线程组,优先级,名称,是否后台线程等。
  7. handler - 由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序。

使用execute执行task时,当线程数未到达核心线程数时,新建核心线程;当到达核心线程数量时新的task放到队列里;当队列满时候,新建非核心线程;当线程总数到达maximumPoolSize ,执行丢弃策略。

线程池生命周期管理

生命周期由ctl是原子整数里面包装的runState维护。

  1. 线程池的状态如下:
    • RUNNING:接受新任务,处理排队任务。
    • SHUTDOWN:不接受新任务,但处理排队的任务。
    • STOP:不接受新任务,不处理排队的任务,中断处理中的任务。
    • TIDYING:所有的任务都已经终止,workerCount为零,线程在TIDYING状态时将运行 terminated()钩子方法。
    • TERMINATED: terminated()已完成。
  • 状态过度如下:
    • RUNNING -> SHUTDOWN:在调用shutdown()时,可能在finalize()中隐式地调用
    • (RUNNING或SHUTDOWN) -> STOP(停止):在调用shutdownNow()时。
    • SHUTDOWN -> TIDYING: 当队列和池都为空时
    • STOP -> TIDYING: 当池空了的时候
    • TIDYING -> TERMINATED:当 terminated()钩子方法完成时

线程生命周期管理

关注下 ctl

ctl是个原子整数,它包装了两个字段:

  • workerCount:表示有效线程数,是允许开始和不允许停止的worker数量;为了将它们打包成一个int,我们将workerCount限制为(2^29) -1(约5亿)个线程,而不是(2^31)-1(20亿)个其他可表示的线程。如果将来有这个问题,可以把变量改成AtomicLong,并调整下面的shift/ask常数。但在没有出现需要之前,这段代码使用int会更快更简单一些。(来自注释)
  • runState:运行状态,表示是否正在运行、关闭等。
    下面的代码我把要用到的变量的32位2进制表示写在注释里(注意,仅认为使用的类型为32位int):
 // 
 private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
 // COUNT_BITS = 29
 private static final int COUNT_BITS = Integer.SIZE - 3;
 // 1左移29位是2^29,再减1的二进制表示如下
 // 0001 1111 1111 1111 1111 1111 1111 1111
 private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

 // runState 存储在高位
 // 1110 0000 0000 0000 0000 0000 0000 0000
 private static final int RUNNING    = -1 << COUNT_BITS;
 // 0000 0000 0000 0000 0000 0000 0000 0000
 private static final int SHUTDOWN   =  0 << COUNT_BITS;
 // 0010 0000 0000 0000 0000 0000 0000 0000
 private static final int STOP       =  1 << COUNT_BITS;
 // 0100 0000 0000 0000 0000 0000 0000 0000
 private static final int TIDYING    =  2 << COUNT_BITS;
 // 0110 0000 0000 0000 0000 0000 0000 0000
 private static final int TERMINATED =  3 << COUNT_BITS;

 // 拆装ctl
 // ~CAPACITY为 1110 0000 0000 0000 0000 0000 0000 0000,
 // ctl与非CAPACITY按位与,相当于拿到高3位,即拆出runState的值
 private static int runStateOf(int c)     { return c & ~CAPACITY; }
 // ctl与CAPACITY按位与,相当于拿到低29位,即拆出来workerCount的值
 private static int workerCountOf(int c)  { return c & CAPACITY; }
 // runState和workerCount按位或,一个高3位为有效值,一个低29为有效值 ,相当于把两个值装到一起。
 private static int ctlOf(int rs, int wc) { return rs | wc; }

 /*
  * 因workerCount处于低位,而runState处于高位,所以直接判断ctl的大小而无需拆ctl即可判断状态值,所以有如下方法
  */
 private static boolean runStateLessThan(int c, int s) {
     return c < s;
 }

 private static boolean runStateAtLeast(int c, int s) {
     return c >= s;
 }
 // 观察可知,SHUTDOWN所有位均为0,仅有RUNNING状态的符号位为1,故小于SHUTDOWN即判断是RUNNING状态
 private static boolean isRunning(int c) {
     return c < SHUTDOWN;
 }

扩展钩子

上面的理解只是理解ThreadPoolExecutor的基础,在此基础上我们再展开来看。

另:线程池配置 [转载自文章底部链接]

  1. DB连接池配置
  • 最小连接数=(平均QPS* QPS平均RT +平均TPS* TPS平均RT)/业务机器数
  • 最大连接数=(峰值QPS* QPS平均RT +峰值TPS* TPS平均RT)/业务机器数;
    如果业务代码中没有另起多线程,那么也可以使用公式:最大连接数= 容器处理请求的线程池大小。

另外,如果需要,也可以在此公式的基础上预留一些buffer。

  1. Java线程池配置
    2.1 CPU密集型任务的线程池配置
    因为cpu执行速度非常快,往往切换线程上下文环境所耗费的时间比执行代码花费的时间更长,所以CPU密集型任务的线程数应该尽量保持和CPU核数一致,以减少线程切换带来的性能损耗。
    2.2 IO密集型任务的线程池配置
    IO密集型任务的主要性能瓶颈在于等待IO结果,当遇到任务中存在从DB中读取数据,从缓存中读取数据时,就可以认为是IO密集型任务。一般而言业务系统配置线程池基本上都会采用此模型配置

参考:云栖社区:ThreadPoolExecutor

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值