一,什么是线程池?
简单来说,管理线程的池子。帮我们重复管理线程,避免创建大量的线程增加开销。
二,为什么用线程池?
-
降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
-
提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
-
提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
三,什么时候用线程池?
-
单个任务处理时间比较短
-
需要处理的任务数量很大
四,解读线程池的原理
A,七个核心参数
1,corePoolSize,核心线程数量
默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中
2,maximumPoolSize,线程数最大值
3,keepLiveTime,多长时间被回收
默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;
4,timeUnit,设置keepLiveTime的时间单位,共7种单位:d, h, m, s, ms, mis, ns
5,workQueue,存放提交至线程池但未被执行的任务
ArrayBlockingQueue(有界队列),是一个用数组实现的有界阻塞队列,按FIFO排序量。
LinkedBlockingQueue(可设置容量队列),基于链表结构的阻塞队列,按FIFO排序任务,容量可以选择进行设置,不设置的话,将是一个无边界的阻塞队列,最大长度为Integer.MAX_VALUE,吞吐量通常要高于ArrayBlockingQuene;newFixedThreadPool线程池使用了这个队列
DelayQueue(延迟队列),是一个任务定时周期的延迟执行的队列。根据指定的执行时间从小到大排序,否则根据插入到队列的先后排序。newScheduledThreadPool线程池使用了这个队列。
PriorityBlockingQueue(优先级队列),是具有优先级的无界阻塞队列;
SynchronousQueue(同步队列),一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQuene,newCachedThreadPool线程池使用了这个队列。
6,threadFactory,创建线程的工厂
用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程设置更有意义的名字。
使用开源框架guava提供的ThreadFactoryBuilder可以快速给线程池里的线程设置有意义的名字,code: new ThreadFactoryBuilder().setNameFormat(“Hello-Engine”).build();
7,rejectedExecutionHandler,拒绝策略设置
CallerRunsPolicy:只要线程池没关闭,就用调用者所在线程来运行任务
AbortPolicy:直接抛RejectedExecutionException 异常
DiscardPolicy:不处理,直接扔了
DiscardOldestPolicy:把队列里待最久的那个任务扔了,并执行当前任务
支持实现自己的 RejectedExecutionHandler 接口自定义策略,如记录日志类。
B,线程池的状态
RUNNING
-
该状态的线程池会接收新任务,并处理阻塞队列中的任务;
-
调用线程池的shutdown()方法,可以切换到SHUTDOWN状态;
-
调用线程池的shutdownNow()方法,可以切换到STOP状态;
SHUTDOWN
-
该状态的线程池不会接收新任务,但会处理阻塞队列中的任务;
-
队列为空,并且线程池中执行的任务也为空,进入TIDYING状态;
STOP
-
该状态的线程不会接收新任务,也不会处理阻塞队列中的任务,而且会中断正在运行的任务;
-
线程池中执行的任务为空,进入TIDYING状态;
TIDYING
-
该状态表明所有的任务已经运行终止,记录的任务数量为0。
-
terminated()执行完毕,进入TERMINATED状态
TERMINATED
-
该状态表示线程池彻底终止
C,任务执行流程
1,execute()方法
a,主要流程图如下:
b,ThreadPoolExecutor执行流程
-
提交一个任务,线程池里存活的核心线程数小于线程数corePoolSize时,线程池会创建一个核心线程去处理提交的任务。
-
如果线程池核心线程数已满,即线程数已经等于corePoolSize,一个新提交的任务,会被放进任务队列workQueue排队等待执行。
-
当线程池里面存活的线程数已经等于corePoolSize了,并且任务队列workQueue也满,判断线程数是否达到maximumPoolSize,即最大线程数是否已满,如果没到达,创建一个非核心线程执行提交的任务。
-
如果当前的线程数达到了maximumPoolSize,还有新的任务过来的话,直接采用拒绝策略处理。
c,源码解读
2,submit()方法
ps: 线程池抛异常了,如何处理?
五,怎样使用线程池?
A,Executors常用方法及问题
1,常用方法四种:
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
工作机制:
适用场景:用于处理CPU密集型的任务,确保CPU在长期被工作线程使用的情况下,尽可能的少的分配线程,即适用执行长期的任务。
newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
工作机制:
适用场景:用于串行执行任务的场景,一个任务一个任务地执行。
newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
工作机制:
适用场景:用于并发执行大量短期的小任务。
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
工作机制:
-
适用场景: 周期性执行任务的场景,需要限制线程数量的场景
2,使用Executors的问题, 来自阿里Java规范的建议:
B,自定义线程池
1,合理配置线程池
通常我们是需要根据这批任务执行的性质来确定的。
-
IO 密集型任务:由于线程并不是一直在运行,所以可以尽可能的多配置线程,比如 CPU 个数 * 2
-
CPU 密集型任务(大量复杂的运算)应当分配较少的线程,比如 CPU 个数相当的大小。
当然这些都是经验值,最好的方式还是根据实际情况测试得出最佳配置。
2,推荐使用Guava提供的ThreadFactoryBuilder来创建线程池