线程池中各个参数分别有什么含义,如何设置最合理
wxin55 2024-11-07 13:12 9 浏览 0 评论
前言
在开发过程中,难免会使用到线程池来处理各种各样的场景。每次的使用,要不是copy之前的代码,要不就上网找个例子,要不就创建后,盲改一下参数,觉得差不多就行了。但我们真正用对了线程池了吗?各个参数是什么意思,分别起什么作用?我们都了解了吗?如果没有的话,咱这次就来好好聊聊 “线程池中各个参数分别有什么含义,如何设置最合理”这个话题
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)
- 1、corePoolSize:核心线程数
核心线程数大小,这个参数含义是在线程池创建后,不管有没有任务,这些线程数是一直存在的,除非设置了allowCoreThreadTimeout。
这好比在公司里的正编员工,这些员工比较稳定,不管公司的活是多是少,这些人都不会被辞退,都是长期存在的
- 2、maximumPoolSize:最大线程数
最大线程数,这个参数含义是线程池允许创建最大的线程数,它在什么时候创建的呢?有什么限制呢?
- 当线程数>=corePoolSize,并且workQueue队列中的任务已满时,线程池会创建新的线程来处理队列中的任务
- 当线程数=maximumPoolSize,并且workQueue队列中的任务已满时,表示线程池已无法承载更多的任务了,此时线程池会根据handler拒绝策略来拒绝任务
这好比公司里的临时工,这些员工并不稳定,当公司活多时,正式员工处理不过来,就会招募一些临时工过来消化这些任务,当这些任务消化得差不多了,这些临时工就会被裁掉,释放资源到社会。
PS:maximumPoolSize设置的值不能小于corePoolSize的值,否则会在程序运行时抛IllegalArgumentException异常
- 3、keepAliveTime:空闲线程存活时间
线程空闲存活的时间,这个参数含义是线程自上一次处理任务到现在的空闲时间。当这个空闲时间>=keepAliveTime时,线程销毁并清出线程池,直到线程池内的线程数量=corePoolSize。但如果allowCoreThreadTimeout=true时,线程会一直被销毁与清出线程池,直至线程数量为0。 - 4、unit:时间单位
时间单位,与keepAliveTime结合着用,keepAliveTime是一个数字,但没单位,而这个参数的含义为这个keepAliveTime添加上一个单位,TimeUnit有以下值:
TimeUnit.DAYS:天
TimeUnit.HOURS:小时
TimeUnit.MINUTES:分
TimeUnit.SECONDS:秒
TimeUnit.MILLISECONDS:毫秒
TimeUnit.MICROSECONDS:微妙
TimeUnit.NANOSECONDS:纳秒
- 5、workQueue:线程池任务队列
线程池任务队列,当核心线程池已经都在执行任务了,无霞应接更多的任务时,这些任务就会放到队列中等待执行。BlockingQueue<T>是一个接口,在同一个包(java.util.concurrent)下有以下的实现类
ArrayBlockingQueue:一个由数组结构组成、遵循FIFO原则的有界阻塞队列。
LinkedBlockingQueue:一个由链表结构组成的无界阻塞队列,如果大量任务堆积在这个队列中,会导致OOM。
SynchronousQueue:同步移交队列,准确说这已经不是一个队列了,它是一个不存储元素的阻塞队列,任务来至即交给工作线程处理,所以最多也只能有maximumPoolSize个任务在执行,多了就要抛异常了。
PriorityBlockingQueue:一个支持优先级排序的有界阻塞队列,任务的优先级由Comparator来决定。
DelayQueue:一个使用优先级队列实现的无界阻塞队列,只有在延迟期满时才能从中提取元素。
LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。与SynchronousQueue类似,还含有非阻塞方法。
LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。
PS:Executors.newFixedThreadPool 采用就是 LinkedBlockingQueue
- 6、threadFactory:创建线程的工厂
创建线程的工厂,这个参数的含义是线程池在创建新的线程时,会使用哪个线程工厂来完成创建,可以通过此方法来设置线程的一些基本属性,如优先级,线程名称,线程类型等。ThreadFactory是一个接口,这里可以传入一个ThreadFactory的子类。 - 7、handler:拒绝策略
拒绝策略,这个参数的含义为:当前线程数=maximumPoolSize 并且队列里面放满了任务时,如果程序继续向线程池提交的任务,此时线程池应该执行怎么样的拒绝策略。RejectedExecutionHandler是一个接口,在同一个包(java.util.concurrent),有以下4种实现方法:
AbortPolicy:拒绝并抛出异常。
CallerRunsPolicy:使用当前调用的线程来执行此任务。
DiscardOldestPolicy:抛弃队列头部(最旧)的一个任务,并执行当前任务。
DiscardPolicy:忽略并抛弃当前任务
PS:线程池的默认策略是 AbortPolicy 拒绝并抛出异常
如何设置最合理
参数的含义介绍完了,但怎么设置这些参数才是最合理的呢?这些参数设置后,是不是适用业务的所有场景呢?下次创建是不是只需要复制就可以呢?是不是很多疑问?没关系,我们一步步分析下去。
我们知道线程池中的两个非常重要的参数:corePoolSize、maximumPoolSize,直接影响执行的性能和提交到线程池的任务能否执行完成。
首先我们对线程池负责执行的任务分为三种情况:
- CPU密集型任务,比如找出1-1000000中的素数
- IO密集型任务,比如文件IO、网络IO
CPU密集型
CPU密集型任务的特点时,线程在执行任务时会一直利用CPU,所以对于这种情况,就尽可能避免发生线程上下文切换。
比如,现在我的电脑只有一个CPU,如果有两个线程在同时执行找素数的任务,那么这个CPU就需要额外的进行线程上下文切换,从而达到线程并行的效果,此时执行这两个任务的总时间为:
任务执行时间*2+线程上下文切换的时间
而如果只有一个线程,这个线程来执行两个任务,那么时间为:
任务执行时间*2
所以对于CPU密集型任务,线程数最好就等于CPU核心数,可以通过以下API拿到你电脑的核心数:
Runtime.getRuntime().availableProcessors()
只不过,为了应对线程执行过程发生缺页中断或其他异常导致线程阻塞的请求,我们可以额外在多设置一个线程,这样当某个线程暂时不需要CPU时,可以有替补线程来继续利用CPU。
所以,对于CPU密集型任务,我们可以设置线程数为:CPU核心数+1
IO密集型
我们在来看IO型任务,线程在执行IO型任务时,可能大部分时间都阻塞在IO上,假如现在有10个CPU,如果我们只设置了10个线程来执行IO型任务,那么很有可能这10个线程都阻塞在了IO上,这样这10个CPU就都没活干了,所以,对于IO型任务,我们通常会设置线程数为:2*CPU核心数
不过,就算是设置为了2*CPU核心数,也不一定是最佳的,比如,有10个CPU,线程数为20,那么也有可能这20个线程同时阻塞在了IO上,所以可以再增加线程,从而去压榨CPU的利用率。
通常,如果IO型任务执行的时间越长,那么同时阻塞在IO上的线程就可能越多,我们就可以设置更多的线程,但是,线程肯定不是越多越好,我们可以通过以下这个公式来进行计算:
线程数 = CPU核心数 *( 1 + 线程等待时间 / 线程运行总时间 )
- 线程等待时间:指的就是线程没有使用CPU的时间,比如阻塞在了IO
- 线程运行总时间:指的是线程执行完某个任务的总时间
按上述公式,如果我们执行的任务IO密集型任务,那么:线程等待时间 = 线程运行总时间,所以:
线程数 = CPU核心数 *( 1 + 线程等待时间 / 线程运行总时间 )
= CPU核心数 *( 1 + 1 )
= CPU核心数 * 2
以上只是理论,实际工作中情况会更复杂,比如一个应用中,可能有多个线程池,除开线程池中的线程可能还有很多其他线程,或者除开这个应用还是一些其他应用也在运行,所以实际工作中如果要确定线程数,最好是压测。
总结
- CPU密集型任务:CPU核心数+1,这样既能充分利用CPU,也不至于有太多的上下文切换成本
- IO型任务:建议压测,或者先用公式计算出一个理论值(理论值通常都比较小)
- 对于核心业务(访问频率高),可以把核心线程数设置为我们压测出来的结果,最大线程数可以等于核心线程数,或者大一点点,比如我们压测时可能会发现500个线程最佳,但是600个线程时也还行,此时600就可以为最大线程数
- 对于非核心业务(访问频率不高),核心线程数可以比较小,避免操作系统去维护不必要的线程,最大线程数可以设置为我们计算或压测出来的结果。
相关推荐
- ES6中 Promise的使用场景?(es6promise用法例子)
-
一、介绍Promise,译为承诺,是异步编程的一种解决方案,比传统的解决方案(回调函数)更加合理和更加强大在以往我们如果处理多层异步操作,我们往往会像下面那样编写我们的代码doSomething(f...
- JavaScript 对 Promise 并发的处理方法
-
Promise对象代表一个未来的值,它有三种状态:pending待定,这是Promise的初始状态,它可能成功,也可能失败,前途未卜fulfilled已完成,这是一种成功的状态,此时可以获取...
- Promise的九大方法(promise的实例方法)
-
1、promise.resolv静态方法Promise.resolve(value)可以认为是newPromise方法的语法糖,比如Promise.resolve(42)可以认为是以下代码的语...
- 360前端一面~面试题解析(360前端开发面试题)
-
1.组件库按需加载怎么做的,具体打包配了什么-按需加载实现:借助打包工具(如Webpack的require.context或ES模块动态导入),在使用组件时才引入对应的代码。例如在V...
- 前端面试-Promise 的 finally 怎么实现的?如何在工作中使用?
-
Promise的finally方法是一个非常有用的工具,它无论Promise是成功(fulfilled)还是失败(rejected)都会执行,且不改变Promise的最终结果。它的实现原...
- 最简单手写Promise,30行代码理解Promise核心原理和发布订阅模式
-
看了全网手写Promise的,大部分对于新手还是比较难理解的,其中几个比较难的点:状态还未改变时通过发布订阅模式去收集事件实例化的时候通过调用构造函数里传出来的方法去修改类里面的状态,这个叫Re...
- 前端分享-Promise可以中途取消啦(promise可以取消吗)
-
传统Promise就像一台需要手动组装的设备,每次使用都要重新接线。而Promise.withResolvers的出现,相当于给开发者发了一个智能遥控器,可以随时随地控制异步操作。它解决了三大...
- 手写 Promise(手写输入法 中文)
-
前言都2020年了,Promise大家肯定都在用了,但是估计很多人对其原理还是一知半解,今天就让我们一起实现一个符合PromiseA+规范的Promise。附PromiseA+规范地址...
- 什么是 Promise.allSettled()!新手老手都要会?
-
Promise.allSettled()方法返回一个在所有给定的promise都已经fulfilled或rejected后的promise,并带有一个对象数组,每个对象表示对应的pr...
- 前端面试-关于Promise解析与高频面试题示范
-
Promise是啥,直接上图:Promise就是处理异步函数的API,它可以包裹一个异步函数,在异步函数完成时抛出完成状态,让代码结束远古时无限回掉的窘境。配合async/await语法糖,可...
- 宇宙厂:为什么前端离不开 Promise.withResolvers() ?
-
大家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发。1.为什么需要Promise.with...
- Promise 新增了一个超实用的 API!
-
在JavaScript的世界里,Promise一直是处理异步操作的神器。而现在,随着ES2025的发布,Promise又迎来了一个超实用的新成员——Promise.try()!这个新方法简...
- 一次搞懂 Promise 异步处理(promise 异步顺序执行)
-
PromisePromise就像这个词的表面意识一样,表示一种承诺、许诺,会在后面给出一个结果,成功或者失败。现在已经成为了主流的异步编程的操作方式,写进了标准里面。状态Promise有且仅有...
- Promise 核心机制详解(promise机制的实现原理)
-
一、Promise的核心状态机Promise本质上是一个状态机,其行为由内部状态严格管控。每个Promise实例在创建时处于Pending(等待)状态,此时异步操作尚未完成。当异步操作成功...
- javascript——Promise(js实现promise)
-
1.PromiseES6开始支持,Promise对象用于一个异步操作的最终完成(包括成功和失败)及结果值的表示。简单说就是处理异步请求的。之所以叫Promise,就是我承诺,如果成功则怎么处理,失败怎...
你 发表评论:
欢迎- 一周热门
- 最近发表
-
- ES6中 Promise的使用场景?(es6promise用法例子)
- JavaScript 对 Promise 并发的处理方法
- Promise的九大方法(promise的实例方法)
- 360前端一面~面试题解析(360前端开发面试题)
- 前端面试-Promise 的 finally 怎么实现的?如何在工作中使用?
- 最简单手写Promise,30行代码理解Promise核心原理和发布订阅模式
- 前端分享-Promise可以中途取消啦(promise可以取消吗)
- 手写 Promise(手写输入法 中文)
- 什么是 Promise.allSettled()!新手老手都要会?
- 前端面试-关于Promise解析与高频面试题示范
- 标签列表
-
- hive行转列函数 (63)
- sourcemap文件是什么 (54)
- display none 隐藏后怎么显示 (56)
- 共享锁和排他锁的区别 (51)
- httpservletrequest 获取参数 (64)
- jstl包 (64)
- qsharedmemory (50)
- watch computed (53)
- java中switch (68)
- date.now (55)
- git-bash (56)
- 盒子垂直居中 (68)
- npm是什么命令 (62)
- python中+=代表什么 (70)
- fsimage (51)
- nginx break (61)
- mysql分区表的优缺点 (53)
- centos7切换到图形界面 (55)
- 前端深拷贝 (62)
- kmp模式匹配算法 (57)
- jsjson字符串转json对象 (53)
- jdbc connection (61)
- javascript字符串转换为数字 (54)
- mybatis 使用 (73)
- 安装mysql数据库 (55)