【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明: Executors 返回的线程池对象的弊端 如下
1 FixedThreadPool 和 SingleThread Pool
允许的请求队列长度为 Integer.MAX_VALUE,可 能会堆积大量的请求,从而导致 OOM。
2 CachedThreadPool 和 ScheduledThreadPool
允许的创建线程数量为 Integer.MAX_VALUE 可能会创建大量的线程,从而导致 OOM。
1、线程池解决了哪些问题?
(1)复用已有的线程资源;
(2)可以对任务限流,控制线程数量,使用不同的任务拒绝策略;
(3)降低频繁的创建和销毁线程;
2、ThreadPoolExecutor构造函数
取自全参数的构造函数代码。
/*
*使用给定的初始名称创建一个新的{@code ThreadPoolExecutor}参数。
*
* @param corePoolSize保留在池中的线程数,即使如果它们空闲,除非设置了{@code allowCoreThreadTimeOut}
* @param maximumPoolSize在允许的最大线程数池
* @param keepAliveTime当线程数大于核心,这是多余的空闲线程的最长时间将终止之前等待新任务。
* @param unit {@code keepAliveTime}参数的时间单位
* @param work将队列用于保存任务之前将其暂存已执行。此队列将仅容纳{@code Runnable}
* 由{@code execute}方法提交的任务。
* @param threadFactory执行程序时要使用的工厂创建一个新线程
* @param handler阻止执行时使用的处理程序因为达到了线程界限和队列容量
* @throws IllegalArgumentException如果以下条件之一成立:<br>
* {@code corePoolSize <0} <br>
* {@code keepAliveTime <0} <br>
* {@code maximumPoolSize <= 0} <br>
* {@code maximumPoolSize <corePoolSize}
*如果{@code workQueue},则抛出NullPointerException
*或{@code threadFactory}或{@code handler}为空
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
3、 线程池执行流程图
4、参数解释
参数 | 类型 | 解释 |
---|---|---|
corePoolSize | int | 核心线程数,如果allowCoreThreadTimeOut为false,即使线程处于空闲状态,也会保留在池中。如果为true,则根据keepAliveTime剔除。 |
maximumPoolSize | int | 最大线程数 |
keepAliveTime | long | 如果线程池当前拥有超过corePoolSize的线程数,那么多余的线程在空闲时间超过keepAliveTime时会被终止 。 |
unit | TimeUnit | 时间单位 |
workQueue | BlockingQueue | 线程等待队列 |
threadFactory | ThreadFactory | 执行程序时要使用工厂创建一个新线程 |
handler | RejectedExecutionHandler | 拒绝策略,当达到了线程界面和队列容量,要使用的处理程序 |
5、部分参数详细解释
5.1、workQueue 等待队列
用于存放提交的任务,队列的实际容量与线程池稳定性相关联。
如果当前线程池任务线程数量小于核心线程池数量,执行器总是优先创建一个任务线程,而不是从线程池中取一个空闲线程。
如果当前线程池任务线程数量大于核心线程池数量,执行器总是优先从线程队列中取一个空闲线程,而不是创建一个任务线程。
如果当前线程池任务线程数量大于核心线程池数量,且池中无空闲任务线程,将会创建一个任务线程,直到超出maximumPoolSize,如果超出maximumPoolSize,则任务将会被拒绝。
主要有三种队列策略:
(1)Direct handoffs 直接握手队列
Direct handoffs的默认选择是 SynchronousQueue,它将任务直接交给线程而不是自己保留。如果没有线程能立即运行它,将构建新的线程。Direct handoffs通常需要无限制的maximumPoolSizes来避免拒绝新提交的任务。
注意:当任务持续以平均提交速度大余平均处理速度时,会导致线程数量会无限增长问题。
(2)Unbounded queues 无界队列(例如未定义容量的LinkedBlockingQueue)
当所有corePoolSize线程繁忙时,使用无界队列将导致新任务在队列中等待,从而导致maximumPoolSize的值没有任何作用,从而导致OOM。
(3)Bounded queues 有界队列(例如ArrayBlockingQueue)
一个有界的队列和有限的maximumPoolSizes配置有助于防止资源耗尽,但是难以控制。队列大小和maximumPoolSizes需要相互权衡。
5.2、threadFactory 线程工厂
默认为Executors.defaultThreadFactory()。
通过提供不同的ThreadFactory,您可以更改线程的名称,线程组,优先级,守护线程状态等。
5.3、RejectedExecutionHandler 拒绝策略
拒绝任务有两种情况:
(1) 线程池已经被关闭;
(2)任务队列已满且maximumPoolSizes已满;
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
预定义了四种处理策略:
AbortPolicy:默认策略,抛出RejectedExecutionException运行时异常;
CallerRunsPolicy:调用当前线程池的所在的线程去执行被拒绝的任务;
DiscardPolicy:直接丢弃新提交的任务;
DiscardOldestPolicy:如果执行器没有关闭,队列头的任务将会被丢弃,然后执行器重新尝试执行任务(如果失败,则重复这一过程);
我们可以自己定义RejectedExecutionHandler,以适应特殊的容量和队列策略场景中。
6、Executors预定义线程池
6.1 newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
队列:workQueue为LinkedBlockingQueue(无界阻塞队列),队列最大值为Integer.MAX_VALUE。如果任务提交速度持续大余任务处理速度,会造成队列大量阻塞。因为队列很大,很有可能在拒绝策略前,内存溢出。
适用场景:可用于Web服务瞬时削峰,但需注意长时间持续高峰情况造成的队列阻塞。
6.2 newCachedThreadPool
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
队列:workQueue 为 SynchronousQueue 同步队列,这个队列类似于一个接力棒,入队出队必须同时传递,因为CachedThreadPool线程创建无限制,不会有队列等待,所以使用SynchronousQueue;
适用场景:快速处理大量耗时较短的任务,如Netty的NIO接受请求时,可使用CachedThreadPool。
6.3 newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
6.4 newScheduledThreadPool
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
ScheduledThreadPoolExecutor可以用来在给定延时后执行异步任务或者周期性执行任务,相对于任务调度的Timer来说,其功能更加强大,Timer只能使用一个后台线程执行任务,而ScheduledThreadPoolExecutor则可以通过构造函数来指定后台线程的个数。
6.5 newWorkStealingPool
public static ExecutorService newWorkStealingPool(int parallelism) {
return new ForkJoinPool
(parallelism,
ForkJoinPool.defaultForkJoinWorkerThreadFactory,
null, true);
}
创建一个线程池,该线程池维护足够的线程以支持给定的并行级别,并且可以使用多个队列来减少争用。并行级别对应于活动参与或可用参与任务处理的最大线程数。实际的线程数可能会动态增长和收缩。工作窃取池无法保证提交的任务的执行顺序。