[java]线程池创建ThreadPoolExecutor

【强制】线程池不允许使用 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、参数解释

参数类型解释
corePoolSizeint核心线程数,如果allowCoreThreadTimeOut为false,即使线程处于空闲状态,也会保留在池中。如果为true,则根据keepAliveTime剔除。
maximumPoolSizeint最大线程数
keepAliveTimelong如果线程池当前拥有超过corePoolSize的线程数,那么多余的线程在空闲时间超过keepAliveTime时会被终止 。
unitTimeUnit时间单位
workQueueBlockingQueue线程等待队列
threadFactoryThreadFactory执行程序时要使用工厂创建一个新线程
handlerRejectedExecutionHandler拒绝策略,当达到了线程界面和队列容量,要使用的处理程序

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);
}

创建一个线程池,该线程池维护足够的线程以支持给定的并行级别,并且可以使用多个队列来减少争用。并行级别对应于活动参与或可用参与任务处理的最大线程数。实际的线程数可能会动态增长和收缩。工作窃取池无法保证提交的任务的执行顺序。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值