常用方法
- newSingleThreadExecutor
创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。
此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
- 优点:单线程的线程池,保证线程的顺序执行
- 缺点:不适合并发
- newFixedThreadPool
创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
- 优点:固定大小线程池,超出的线程会在队列中等待
- 缺点:不支持自定义拒绝策略,大小固定,难以扩展
- newCachedThreadPool
创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
- 优点:很灵活,弹性的线程池线程管理,用多少线程给多大的线程池,不用后及时回收,用则新建
- 缺点:一旦线程无限增长,会导致内存溢出
- newScheduledThreadPool
创建一个定长线程池,支持定时及周期性任务执行
- 优点:一个固定大小线程池,可以定时或周期性的执行任务
- 缺点:任务是单线程方式执行,一旦一个任务失败其他任务也受影响
总结
- 以上线程池都不支持自定义拒绝策略。
- newFixedThreadPool 和 newSingleThreadExecutor:主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至 OOM。
- newCachedThreadPool 和 newScheduledThreadPool:主要问题是线程数最大数是 Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至 OOM。
参数
我们来分析一下ThreadPollExecutor这些参数的含义
corePoolSize:
线程池启动后,在池中保持的线程的最小数量。需要说明的是线程数量是逐步到达corePoolSize值的。例如corePoolSize被设置为10,而任务数量只有5,则线程池中最多会启动5个线程,而不是一次性地启动10个线程。
maxinumPoolSize:
线程池中能容纳的最大线程数量,如果超出,则使用RejectedExecutionHandler拒绝策略处理。
keepAliveTime:
线程的最大生命周期。这里的生命周期有两个约束条件:一:该参数针对的是超过corePoolSize数量的线程;二:处于非运行状态的线程。举个例子:如果corePoolSize(最小线程数)为10,maxinumPoolSize(最大线程数)为20,而此时线程池中有15个线程在运行,过了一段时间后,其中有3个线程处于等待状态的时间超过keepAliveTime指定的时间,则结束这3个线程,此时线程池中则还有12个线程正在运行。
unit:
这是keepAliveTime的时间单位,可以是纳秒,毫秒,秒,分钟等。
workQueue:
任务队列。当线程池中的线程都处于运行状态,而此时任务数量继续增加,则需要一个容器来容纳这些任务,这就是任务队列。这个任务队列是一个阻塞式的单端队列。
threadFactory:
定义如何启动一个线程,可以设置线程的名称,并且可以确定是否是后台线程等。
handler:
拒绝任务处理器。由于超出线程数量和队列容量而对继续增加的任务进行处理的程序。
线程的管理过程
首先创建一个线程池,然后根据任务的数量逐步将线程增大到corePoolSize,如果此时仍有任务增加,则放置到workQueue中,直到workQueue爆满为止,然后继续增加池中的线程数量(增强处理能力),最终达到maxinumPoolSize。那如果此时还有任务要增加进来呢?这就需要handler来处理了,或者丢弃新任务,或者拒绝新任务,或者挤占已有的任务。在任务队列和线程池都饱和的情况下,一旦有线程处于等待(任务处理完毕,没有新任务)状态的时间超过keepAliveTime,则该线程终止,也就是说池中的线程数量会逐渐降低,直至为corePoolSize数量为止。