ThreadPoolExecutor是一个 ExecutorService,它使用可能的几个池线程之一执行每个提交的任务,通常使用 Executors 工厂方法配置。它有四个构造方法,其中参数最多的那个包括以下7个参数:线程池核心线程数、线程池最大数、空闲线程存活时间、时间单位、线程池所使用的缓冲队列、线程池创建线程使用的工厂和线程池对拒绝任务的处理策略。
关于线程池的好处:
- 减少了线程的创建和销毁开销,提高了性能和效率。
- 避免了线程数量过多而导致的系统资源耗尽和线程调度开销增大的问题。
- 允许调整线程池大小,以满足不同应用程序的需求。
- 可以提高代码的可维护性和可重用性,避免了线程相关的错误,使得代码更加健壮和可靠。
解释一下不同ThreadPoolExecutor中不同参数的作用
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
此构造方法的参数如下:
-
int corePoolSize:核心线程数(正式员工人数)正常情况下,我们的系统应该能同时工作的线程数(随时就绪的状态),线程池中一直存在的线程数,即使线程闲置。
-
int maximumPoolSize:(最大线程数 => 哪怕任务再多,你也只能最多招5个人),极限情况下,我们的线程池最多有多少个线程?,线程池中允许存在的最大线程数。
-
long keepAliveTime:(空闲线程存活时间),非核心线程在没有任务的情况下,过多久要删除(如果留着的话浪费系统资源),从而释放无用的线程资源。非核心线程的空闲线程存活时间,单位:毫秒。
-
TimeUnit unit:(空闲线程存活时间的单位)存活时间的单位,可选的单位包括:TimeUnit.MILLISECONDS、TimeUnit.SECONDS、TimeUnit.MINUTES、TimeUnit.HOURS、TimeUnit.DAYS(时分秒)等等。
-
BlockingQueue workQueue:(工作队列)用于存放给线程执行的任务,存在一个队列的长度(一定要设置,不要说队列长度无限,因为也会占用资源)。用于存放线程任务的阻塞队列,当线程池中的线程数达到了corePoolSize,而阻塞队列中任务满了时,线程池会创建新的线程,直到达到maximumPoolSize,此时达到线程池最大容量,若阻塞队列不为空,新加入的任务将会被拒绝,同时也可以通过设置RejectedExecutionHandler处理满了阻塞队列和饱和的情况。
-
ThreadFactory threadFactory:(线程工厂)线程创建工厂,控制每个线程的生成、线程的属性(比如线程名),用于创建新的线程,可以自定义ThreadFactory。
-
RejectedExecutionHandler handler:(拒绝策略)线程池拒绝策略,任务队列满的时候,我们采取什么措施,比如抛异常、不抛异常、自定义策略。当任务无法处理时,会根据设置的拒绝策略进行处理,可选的策略有:AbortPolicy(直接抛出异常,终止程序的执行)、CallerRunsPolicy(让当前的线程来处理该任务)、DiscardPolicy(直接丢弃该任务),DiscardOldestPolicy(丢弃队列中已经存在最久的任务,将当前任务插入队列尝试提交)。
工作机制
假设核心线程数、最大线程数、任务队列为以下的值:
corePoolSize = 2 ;maximumPoolSize = 4 ,workQueue.size = 2
1、开始时:没有任务的线程,也没有任何的任务。
刚开始的核心线程数、最大线程数、任务队列中分别存在的数量为:
corePoolSize = 0 ;maximumPoolSize = 0 ,workQueue.size = 0
来了第一个任务,发现正式的员工人数能处理,来了一个,员工直接处理这个任务。
2、第一个任务到来时的核心线程数、最大线程数、任务队列中分别存在的数量为:
corePoolSize = 1 ;maximumPoolSize = 1 ,workQueue.size = 0
又来了一个任务,发现我们的员工没有达到正式员工数,再来一个员工直接处理这个任务。
3、第二个任务到来时的核心线程数、最大线程数、任务队列中分别存在的数量为:
corePoolSize = 2 ;maximumPoolSize = 2 ,workQueue.size = 0
(一个人处理一个任务,一线程一任务)
又来了一个任务,但是我们正式员工数已经满了(当前线程数 = corePoolSize = 2),将最新来的任务放到任务队列(最大长度 workQueue.size 是 2) 里等待,而不是再加新员工。
4、第三、四个任务到来时的核心线程数、最大线程数、任务队列中分别存在的数量为:
corePoolSize = 2 ;maximumPoolSize = 2 ,workQueue.size = 2
又来了一个任务,但是我们的任务队列已经满了(当前线程数 大于了 corePoolSize=2,己有任务数 = 最大长度 workQueue.size = 2),新增线程(maximumPoolSize = 4)来处理任务,而不是丢弃任务。
5、第五个任务到来时的核心线程数、最大线程数、任务队列中分别存在的数量为:
corePoolSize = 2 ;maximumPoolSize = 3 ,workQueue.size = 2
(找了一个临时工处理这个队新来的这个任务)
当到了任务7,但是我们的任务队列已经满了、临时工也招满了(当前线程数 = maximumPoolSize = 4,已有任务数 = 最大长度 workQueue.size = 2) 调用RejectedExecutionHandler 拒绝策略来处理多余的任务。
6、(没有画图了,因为和第五步的一样)等到第六个任务到来时的核心线程数、最大线程数、任务队列中分别存在的数量为:
corePoolSize = 2 ;maximumPoolSize = 4 ,workQueue.size = 2
(再找了一个临时工处理这个队列任务中最前面的任务4,然后这个第六个新来的线程就进入到任务队列中等待)
7、等到第七个任务到来时的核心线程数、最大线程数、任务队列中分别存在的数量为:
corePoolSize = 2 ;maximumPoolSize = 4 ,workQueue.size = 2
(此时在核心线程数、最大线程数以及任务队列中都占满了,以及无法接收新的任务了,所以说只能拒绝任务7,注意:下图中任务队列是满的,这里少画了两个任务,他们存在于任务队列中无人处理,等待被执行)
最后,如果当前线程数超过 corePoolSize (正式员工数),又没有新的任务给他,那么等 keepAliveTime 时间达到后,就可以把这个线程释放。
如何设置他们的具体值?
- corePoolSize(核心线程数=>正式员工数):正常情况下,可以设置为 2 - 4
- maximumPoolSize(最大线程数):设置为极限情况,设置为 <= 4
- keepAliveTime(空闲线程存活时间):一般设置为秒级或者分钟级
- TimeUnit unit(空闲线程存活时间的单位):分钟、秒
- workQueue(工作队列):结合实际请况去设置,可以设置为20 (2n+1)
- threadFactory(线程工厂):控制每个线程的生成、线程的属性(比如线程名)
- RejectedExecutionHandler(拒绝策略):抛异常,标记数据库的任务状态为 “任务满了已拒绝”
一般情况下,任务分为IO密集型和计算密集型两种。
- 计算密集型(使用cpu去进行大量的运算):吃CPU,比如音视频处理、图像处理(GPU)、数学计算等,一般是设置corePoolSize为CPU的核数 +1 (空余线程),可以让每个线程都利用好CPU的每个核,而且线程之间不用频繁切换(减少打架、减少开销)
- IO密集型:吃带宽/内存/硬盘的读写资源,corePoolSize可以设置大一点,一般经验值是 2n 左右,但是建议以 IO 的能力为主。
各位下次再见。