一、线程池的核心参数
(1)int corePoolSize 线程池中的核心线程数量
allowCoreThreadTimeOut;允许核心线程超时销毁;
boolean prestartCoreThread(),初始化一个核心线程;
int prestartAllCoreThreads(),初始化所有核心线程;
(2)int maximumPoolSize 线程池中允许的最大线程数
当核心线程全部繁忙且任务队列存满之后,线程池会临时追加线程,直到总线程数达到maximumPoolSize这个上限;
(3)long keepAliveTime 线程空闲超时时间
如果一个线程处于空闲状态,并且当前的线程数量大于corePoolSize,那么在指定时间后,这个空闲线程会被销毁;
(4)TimeUnit unit keepAliveTime的时间单位 (天、小时、分、秒......)
(5)BlockingQueue <Runnable> workQueue 任务队列
当核心线程全部繁忙时,任务存放到该任务队列中,等待被核心线程来执行;
(6)ThreadFactory threadFactory 线程工厂
用于创建线程,一般采用默认的线程工厂即可,也可以自定义实现;
Executors.defaultThreadFactory(),
(7)RejectExecutionHandler handler 拒绝策略(饱和策略)
当任务太多来不及处理时,如何“拒绝”任务?
当以下情况发生时,就需要“拒绝”掉新提交过来的任务:
<1>. 核心线程corePoolSize正在执行任务;
<2>. 线程池的任务队列workQueue已满;
<3>.线程池中的线程数达到maximumPoolSize时;
JDK内部提供了四种拒绝策略:
<1>. AbortPolicy:丢弃任务并抛出RejectedExecutionException异常,这是默认的拒绝策略;
<2>. DiscardPolicy:直接丢弃任务,不抛出异常,没有任何提示;
<3>. DiscardOldestPolicy:丢弃任务队列中靠最前的任务,当前提交的任务不会丢弃;
<4>. CallerRunsPolicy: 交由任务的调用线程(提交任务的线程)来执行当前任务;
除了上面的四种拒绝策略,还可以通过实现RejectedExecutionHandler接口,实现自定义的拒绝策略;
二、线程池的执行流程
当我们提交一个新任务到线程池时,具体的执行流程:
1. 当我们提交任务,线程池会根据corePoolSize大小创建线程来执行任务;
2. 当任务的数量超过corePoolSize数量,后续的任务将会进入阻塞队列阻塞排队;
3. 当阻塞队列也满了之后,那么将会继续创建(maximumPoolSize-corePoolSize)个数量的线程来执行任务,如果任务处理完成,maximumPoolSize-corePoolSize个额外创建的线程等待 keepAliveTime之后被自动销毁;
4. 如果达到maximumPoolSize,阻塞队列还是满的状态,那么将根据不同的拒绝策略进行拒绝处理;
三、线程的核心参数怎么设计?
1.CPU密集型
线程数 = CPU核心线程数 + 1;
这种任务主要是消耗CPU资源, 比如像加解密、压缩、计算等一系列需要大量耗费 CPU 资源的任务;
+1,比 CPU 核心数多出来的一个线程是为了防止线程偶发的缺页中断,或者其它原因导致的任务暂停而带来的影响。一旦任务暂停,CPU 就会处于空闲状态,而在这种情况下多出来的一个线程就可以充分利用 CPU 的空闲时间;
2.IO密集型
线程数 = CPU核心线程数 * 2;
这种任务会有大部分时间在进行IO操作,比如像MySQL数据库、文件读写、网络通信等任务,这类任务不会特别消耗CPU资源,但是IO操作比较耗时,会占用比较多时间;
线程在处理IO的时间段内不会占用CPU,这时就可以将CPU交出给其它线程使用,因此在IO密集型任务的应用中,可以多配置一些线程;
基本原则:
1. 线程执行时间越多,就需要越少的线程;
2. 线程等待时间越多,就需要越多的线程