文章目录
- 1.为什么要使用线程池?
- 2.你们哪些地方会使用到线程池?
- 3.线程池有哪些作用?
- 4.线程池的创建方式?
- 5.线程池底层是如何实现复用的?
- 6.ThreadPoolExecutor 核心参数有哪些 ?
- 7.线程池创建的线程会一直在运行状态吗?
- 9.线程池底层 ThreadPoolExecutor 底层实现原理
- 10.线程池队列满了,任务会丢失吗 ?
- 11.线程池如何合理配置参数 ?
1.为什么要使用线程池?
因为频繁的开启线程或者停止线程, 线程需要重新被 CPU 从就绪到运行状态调度, 需要发生CPU 的上下文切换, 效率非常低。
2.你们哪些地方会使用到线程池?
实际开发项目中 禁止自己 new 线程,使用线程池来统一维护管理。
案例分享:
项目中异步发邮件和发短信就是用到了多线程,但是呢,我们自己如果通过new thread线程异步的去发短信或者邮件,这样会存在隐患,导致线程没有使用到线程池进行维护和统一管理。因此,我们采用线程池的方案,使用springboot整合线程池,实现异步发短信或者邮件。当项目比较小时可以采用此方案,如果,项目比较大做异步时,建议采用使用MQ异步。
3.线程池有哪些作用?
核心点: 复用机制 提前创建好固定的线程一直在运行状态 实现复用 限制线程创建数量。
1.降低资源消耗: 通过池化技术重复利用已创建的线程, 降低线程创建和销毁造成的损耗。
2.提高响应速度: 任务到达时, 无需等待线程创建即可立即执行。
3.提高线程的可管理性: 线程是稀缺资源, 如果无限制创建, 不仅会消耗系统资源, 还会因
为线程的不合理分布导致资源调度失衡, 降低系统的稳定性。 使用线程池可以进行统一的分
配、 调优和监控。
4.提供更多更强大的功能: 线程池具备可拓展性, 允许开发人员向其中增加更多的功能。 比
如延时定时线程池 ScheduledThreadPoolExecutor, 就允许任务延期执行或定期执行。
4.线程池的创建方式?
首先线程池的创建方式,我们可以通过JDK原生自带的方式Executors,提供了4个API,有可缓存线程池、可定长度线程池、可定时线程池、单例线程池;但是非常遗憾,阿里巴巴java开发手册他不推荐我们使用JDK源码中自带的线程池,为什么呢,因为,我们的线程池底层都是基于 ThreadPoolExecutor 构造函数封装的,而 ThreadPoolExecutor构造函数中,底层都是采用无界队列缓存我们的任务的,会无限缓存任务容易发生内存溢出,会导致我们最大线程数会失效。
Executors.newCachedThreadPool(); 可缓存线程池
Executors.newFixedThreadPool(); 可定长度 限制最大线程数
Executors.newScheduledThreadPool() ; 可定时
Executors.newSingleThreadExecutor(); 单例
底层都是基于 ThreadPoolExecutor 构造函数封装
5.线程池底层是如何实现复用的?
本质思想:创建一个线程,不会立马停止或者销毁而是一直实现复用。
- 提前创建固定大小的线程且一直保持在正在运行状态;(可能会非常消耗 cpu 的资源)
- 当需要线程执行任务,将该任务提交缓存在并发队列中;如果缓存队列满了,则会执行拒绝策略;
-
- 正在运行的线程从并发队列中获取任务执行,从而实现多线程复用
6.ThreadPoolExecutor 核心参数有哪些 ?
corePoolSize:核心线程数量 一直正在保持运行的线程
maximumPoolSize:最大线程数,线程池允许创建的最大线程数。
keepAliveTime:超出 corePoolSize 后创建的线程的存活时间。
unit:keepAliveTime 的时间单位。
workQueue:任务队列,用于保存待执行的任务。
threadFactory:线程池内部创建线程所用的工厂。
handler:任务无法执行时的处理器。
7.线程池创建的线程会一直在运行状态吗?
不会
例如:配置核心线程数 corePoolSize 为 2 、最大线程数 maximumPoolSize 为 5, 我们可以通过配置超出 corePoolSize 核心线程数后创建的线程的存活时间,例如为 60s, 在 60s 内没有核心线程一直没有任务执行,则会停止该线程。
8.为什么阿里巴巴不建议使用 Executors ?
因为默认的 Executors 线程池底层是基于 ThreadPoolExecutor 构造函数封装的,采用无界队列存放缓存任务,会无限缓存任务容易发生内存溢出,会导致我们最大线程数会失效。
内存溢出与最大线程数会失效:
源码中,多线程缓存队列采用的是LinkedBlockingQueue,而LinkedBlockingQueue的缓存队列大小是无界的(Integer.MAX_VALUE),缓存队列中会一直会有新的线程加入进来,当达到服务器的瓶颈时,就会发生内存溢出。最大线程数开启的前提是缓存队列容量慢的时候,采用开启最大线程数。
9.线程池底层 ThreadPoolExecutor 底层实现原理
10.线程池队列满了,任务会丢失吗 ?
11.线程池如何合理配置参数 ?
自定义线程池就需要我们自己配置最大线程数 maximumPoolSize,为了高效的并发运行,当 然这个不能随便设置。这时需要看我们的业务是 IO 密集型还是 CPU 密集型。
CPU 密集型 CPU 密集的意思是该任务需要大量的运算,而没有阻塞,CPU 一直全速运行。CPU 密集任 务只有在真正的多核 CPU 上才可能得到加速(通过多线程),而在单核 CPU 上,无论你开几 个模拟的多线程该任务都不可能得到加速,因为 CPU 总的运算能力就那些。
CPU 密集型任务配置尽可能少的线程数量:以保证每个 CPU 高效的运行一个线程。
一般公式:(CPU 核数+1)个 线程的线程池
IO 密集型
I0 密集型,即该任务需要大量的 IO,即大量的阻塞。在单线程上运行 I0 密集型的任务会导 致浪费大量的 CPU 运算能力浪费在等待。 所以在 IO 密集型任务中使用多线程可以大大的加速程序运行,即使在单核 CPU 上,这种加 速主要就是利用了被浪费掉的阻塞时间。
I0 密集型时,大部分线程都阻寒,故需要多配置线程数:
公式: CPU 核数 * 2 CPU 核数 / (1 - 阻塞系数) 阻塞系数 在 0.8~0.9 之间
查看 CPU 核数: System.out.println(Runtime.getRuntime().availableProcessors());