-
在 Hotspot
JVM的线程模型中,Java线程被 一对一
映射为 内核线程
- Java使用线程执行程序时,需要创建一个内核线程,当该Java线程被终止时,这个内核线程也会被回收
- Java线程的创建和销毁将会消耗一定的计算机资源,从而增加系统的性能开销
- 大量创建线程也会给系统带来性能问题,线程会抢占内存和CPU资源,可能会发生内存溢出、CPU超负载等问题
-
线程池:即可以 提高线程复用
,也可以 固定最大线程数
,防止无限制地创建线程
-
当程序提交一个任务需要一个线程时,会去线程池查找是否有 空闲
的线程
-
如果有,则直接使用线程池中的线程工作,如果没有,则判断当前已创建的线程数是否超过 最大线程数
-
如果未超过,则创建新线程,如果已经超过,则进行 排队等待
或者直接 抛出异常
线程池框架Executor
-
Java最开始提供了 ThreadPool
来实现线程池,为了更好地实现 用户级的线程调度
,Java提供了一套 Executor
框架
-
Executor框架包括了 ScheduledThreadPoolExecutor
和 ThreadPoolExecutor
两个 核心
线程池, 核心原理一样
-
ScheduledThreadPoolExecutor用来 定时
执行任务,ThreadPoolExecutor用来执行被提交的任务
Executors
-
Executors利用 工厂模式
实现了4种类型的ThreadPoolExecutor
-
不推荐使用
Executors,因为会忽略很多线程池的参数设置,容易导致 无法调优
,产生 性能问题
或者 资源浪费
- 推荐使用ThreadPoolExecutor自定义参数配置
类型 |
特性 |
newCachedThreadPool |
线程池大小 不固定
,可灵活回收空闲线程,若无可回收,则新建线程 |
newFixedThreadPool |
线程池大小 固定
,当有新任务提交,线程池中如果有空闲线程,则立即执行,
否则新的任务会被 缓存
在一个 任务队列
中,等待线程池释放空闲线程 |
newScheduledThreadPool |
定时线程池,支持 定时
或者 周期性
地执行任务 |
newSingleThreadExecutor |
只创建一个线程,保证所有任务按照指定顺序( FIFO
/ LIFO
/ 优先级
)执行 |
ThreadPoolExecutor
// 构造函数
public ThreadPoolExecutor(int corePoolSize, // 线程池的核心线程数
int maximumPoolSize, // 线程池的最大线程数
long keepAliveTime, // 当线程数大于核心线程数时,多余的空闲线程存活的最长时间
TimeUnit unit, // 时间单位
BlockingQueue<Runnable> workQueue, // 任务队列,用来存储等待执行的任务
ThreadFactory threadFactory, // 线程工厂,用来创建线程,用默认即可
RejectedExecutionHandler handler // 拒绝策略,当提交的任务过多而不能及时处理时,可以定制拒绝策略
)
-
在创建完线程池之后,默认情况下,线程池 并没有任何线程
,等到有任务来才创建线程去执行任务
-
如果调用 prestartAllCoreThreads
或者 prestartCoreThread
,可以提前创建等于 核心线程数
的线程数量,称为 预热
-
当创建的线程数 等于corePoolSize
,提交的任务会被加入到设置的 阻塞队列
中
-
当阻塞队列 满
了,会创建线程执行任务,直到线程池中的数量 等于maximumPoolSize
-
当线程数 等于maximumPoolSize
,新提交的任务无法加入到阻塞队列,也无法创建 非核心
线程 直接执行
-
如果没有为线程池设置拒绝策略,线程池会抛出 RejectedExecutionException
,拒绝接受该任务
-
当线程数 超过corePoolSize
,在某些线程处理完任务后,如果等待keepAliveTime后仍然 空闲
,那么该线程将会被 回收
-
回收线程时, 不会区分
是核心线程还是非核心线程,直到线程池中线程的数量等于corePoolSize,回收过程才会停止
-
默认情况下,当线程数 小于等于corePoolSize
时,是不会触发回收过程的,因此
非核心业务线程池的空闲线程会长期存在
-
可以通过 allowCoreThreadTimeOut
方法设置:包括核心线程在内的 所有线程
,在空闲keepAliveTime后会被回收
线程池的线程分配流程
计算线程数量
多线程执行的任务类型分为 CPU密集型
和 IO密集型
CPU密集型
-
该类型任务的消耗主要是CPU资源,可以将线程数设置为 N(CPU核心数)+1
-
+1是为了防止线程偶发的缺页中断,或者其他原因导致的 任务暂停
而带来的影响,+1能够更充分地利用CPU的空闲时间
IO密集型
-
该类型任务在运行时,系统会用大部分的时间来处理 IO交互
-
线程在处理IO的时间段内是 不会占用
CPU来处理,此时可以将CPU交出给其他线程使用,可以先设置为 2N
通用场景
线程数 = N * (1+ WT/ST)
N = CPU核数
WT = 线程等待时间
ST= 线程运行时间
可以根据业务场景,先简单地选择 N+1
或者 2N
,然后进行 压测
,最后依据压测结果进行调整
原文
http://zhongmingmao.me/2019/08/31/java-performance-threadpool-size/