如果采用同步的方法,用户会等很久才能等到系统的响应,这样的体验显然不好
于是,我们引入异步,即用户只需要提交任务就好,系统会另开一个新线程去处理这个任务
那么,怎么优雅的实现开新线程这个过程呢?我们会遇到以下问题:
- 任务队列的最大容量是多少呢?
- 怎么控制队列长度不超过最大容量呢?
- 程序如何葱任务队列中取出任务来执行呢?
- 任务队列的流程如何实现呢?
- 怎么保证程序最多同时执行多少个任务呢?
- 。。。。。。?
所以,我们要使用线程池
线程池(Thread Pool) 是一种并发编程中常用的技术,用于管理和重用线程。它由线程池管理器、工作队列和线程池线程组成。
线程池的基本概念是,在应用程序启动时创建一定数量的线程,并将它们保存在线程池中。当需要执行任务时,从线程池中获取一个空闲的线程,将任务分配给该线程执行。当任务执行完毕后,线程将返回到线程池,可以被其他任务复用。
为什么需要线程池?
- 线程的管理复杂 (何时新增线程?何时减少空闲线程?)
- 任务存取复杂 (何时接受任务?何时拒绝任务?怎么保证大家不会抢到同一个任务?)
线程池的优势
- 降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗
- 提高响应速度:当任务到达时,任务可以不需要等到线程创建就能立即执行
- 提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
- 实现消峰:当请求突然大量增多时,系统也能平稳运行(这并不是线程池的独占优势,阻塞队列、消息队列等亦能实现消峰)
线程池的创建与参数
大多数程序都有一个基本的默认线程池,但是当程序复杂后多种任务都混合在同一线程池种,且不够灵活。所以我们一般会手动去创建线程池。
怎么更好的理解线程池呢?
我们可以把线程池想象成一个公司,提交到线程池的任务即是工作,线程便是公司中的员工。
那么,我们在这个理解下,来看线程池的参数如何理解
线程池创建时的构造函数参数如下:
1 | public ThreadPoolExecutor(int corePoolSize, |
其中每个参数的意思可以参考源码:
corePoolSize
:核心线程数,即会长期存在的线程数,也就是不会被开除的正式员工maximumPoolSize
:最大线程数,当任务较多时,通过创建新的临时线程所能达到的最大线程数,也就是公司缺人时紧急招来的临时员工keepAliveTime
,unit
:二者共同组成临时线程的存活时间,当临时线程空闲超过这个时间后,会被销毁,也就是临时员工多久不干活就会被开除workQueue
:存放任务的一个阻塞队列,也就是工作备忘录threadFactory
:线程工程,可以定制线程属性(名称、优先级等),跟踪和监控线程handler
:拒绝策略,默认当队列满时拒绝任务(也可以设置为重新排队等策略)
线程池的工作方式
想要知道各个参数该怎么设置,就得先知道线程池的工作方式,接下来我们将分几种情况来讨论:
刚开始,没有任何线程和任务
接到新的任务(工作)后,线程池会创建一个新线程(招聘一个正式员工)来完成这个任务线程数已达到
corePoolSize
,即正式员工已经招满了
新来的任务会被放进workQueue
中,也就是在备忘录中记下这个任务workQueue
满了,即备忘录已经写满,不能再写了
线程池会创建临时线程,也就是招聘临时工线程数达到
maximumPoolSize
,即员工总数也满了
此时临时线程也不会再被创建,多的任务会按照拒绝策略handler
处理,默认是直接拒绝临时线程的空闲时间达到
keepAliveTime
后,线程会被消耗
此时临时线程会被销毁,直到线程数达到corePoolSize
设置合理的线程池参数
设计线程池参数时,需要考虑任务是 IO 密集型还是计算密集型
计算密集型
吃CPU,比如音视频处理、图像处理、数学计算等
一般将corePoolSize
设置为 CPU 核心数加一,“加一”可以理解为一个备用线程,来处理其他任务。这样做可以充分利用每一个 CPU 核心,减少线程间的频繁切换,降低开销。
maximumPoolSize
的设定没有严格的规则,一般可以设置为核心线程数的两倍到三倍。IO 密集型
主要消耗带宽或内存硬盘的读写资源,对 CPU 的利用率不高,如查询数据库或等待网络消息传输
这种情况下可以适当增大corePoolSize
的值,因为 CPU 本来就是空闲的
提交任务
因为有了线程池的封装,所以提交任务就很简单了,如下:
1 | CompletableFuture runAsync(Runnable runnable, Executor executor) |
runnable
是要执行的任务executor
是之前创建好的线程池,若不传入这个参数,则是使用系统默认的线程池