线程池ThreadPoolExecutor学习笔记

87 阅读6分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第6天,点击查看活动详情

线程池是什么

线程池(Thread Pool)是一种基于池化思想管理线程的工具,经常出现在多线程服务器中;线程过多会带来额外的开销,其中包括创建销毁线程的开销、调度线程的开销等等,同时也降低了计算机的整体性能。线程池维护多个线程,等待监督管理者分配可并发执行的任务。这种做法,一方面避免了处理任务时创建销毁线程开销的代价,另一方面避免了线程数量膨胀导致的过分调度问题,保证了对内核的充分利用。

使用线程池可以带来一系列好处:

  • 降低资源消耗:通过池化技术重复利用已创建的线程,降低线程创建和销毁造成的损耗。
  • 提高响应速度:任务到达时,无需等待线程创建即可立即执行。
  • 提高线程的可管理性:线程是稀缺资源,如果无限制创建,不仅会消耗系统资源,还会因为线程的不合理分布导致资源调度失衡,降低系统的稳定性。使用线程池可以进行统一的分配、调优和监控。
  • 提供更多更强大的功能:线程池具备可拓展性,允许开发人员向其中增加更多的功能。比如延时定时线程池ScheduledThreadPoolExecutor,就允许任务延期执行或定期执行。

线程池解决的问题是什么

线程池解决的核心问题就是资源管理问题。在并发环境下,系统不能够确定在任意时刻中,有多少任务需要执行,有多少资源需要投入。

912883e51327e0c7a9d753d11896326511272.png

一、常见的线程池

  1. newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程,线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。

  2. newFixedThreadPool创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

  3. newScheduledThreadPool:创建一个定长线程池,支持定时及周期性任务执行,延迟执行。

  4. newSingleThreadExecutor:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

  5. ScheduledThreadPoolExecutor继承了ThreadPoolExecutor,继承了线程池的特性,主要用来在给定的延迟之后运 行任务,或者定期执行任务

    class ScheduledThreadPoolExecutor extends ThreadPoolExecutor implements ScheduledExecutorService

    public ScheduledThreadPoolExecutor(int corePoolSize) { super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue()); }

ScheduledThreadPoolExecutor会把待调度的任务(ScheduledFutureTask) 放到一个DelayQueue中。线程池中的线程从DelayQueue中获取ScheduledFutureTask,然后执行任务。

二、线程池核心参数

  1. corePoolSize(必):核心线程数。默认情况下,核心线程会一直存活,但是当将 allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。
  2. maximumPoolSize(必):线程池所能容纳的最大线程数。当活跃线程数达到该数值后,后续的新任务将会阻塞。
  3. keepAliveTime(必):线程闲置超时时长。如果超过该时长,非核心线程就会被回收。如果将 allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。
  4. unit(必):指定 keepAliveTime 参数的时间单位。常用的有:TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分)。
  5. workQueue(必):任务队列。通过线程池的 execute() 方法提交的 Runnable 对象将存储在该参数中。其采用阻塞队列实现。
  6. threadFactory(可选):线程工厂。用于指定为线程池创建新线程的方式。
  7. handler(可选):拒绝策略。当达到最大线程数时需要执行的饱和策略。

三、线程池任务队列

  1. ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列(数组结构可配合指针实现一个环形队列)。
  2. LinkedBlockingQueue: 一个由链表结构组成的有界阻塞队列,在未指明容量时,容量默认为 Integer.MAX_VALUE。
  3. PriorityBlockingQueue: 一个支持优先级排序的无界阻塞队列,对元素没有要求,可以实现 Comparable 接口也可以提供 Comparator 来对队列中的元素进行比较。跟时间没有任何关系,仅仅是按照优先级取任务。
  4. DelayQueue:类似于PriorityBlockingQueue,是二叉堆实现的无界优先级阻塞队列。要求元素都实现 Delayed 接口,通过执行时延从队列中提取任务,时间没到任务取不出来。
  5. SynchronousQueue: 一个不存储元素的阻塞队列,消费者线程调用 take() 方法的时候就会发生阻塞,直到有一个生产者线程生产了一个元素,消费者线程就可以拿到这个元素并返回;生产者线程调用 put() 方法的时候也会发生阻塞,直到有一个消费者线程消费了一个元素,生产者才会返回。
  6. LinkedBlockingDeque: 使用双向队列实现的有界双端阻塞队列。双端意味着可以像普通队列一样 FIFO(先进先出),也可以像栈一样 FILO(先进后出)。
  7. LinkedTransferQueue: 它是ConcurrentLinkedQueue、LinkedBlockingQueue 和 SynchronousQueue 的结合体,但是把它用在 ThreadPoolExecutor 中,和 8、LinkedBlockingQueue 行为一致,但是是无界的阻塞队列。

四、线程池拒绝策略

  1. AbortPolicy(默认):丢弃任务并抛出 RejectedExecutionException 异常。
  2. CallerRunsPolicy:由调用线程处理该任务。
  3. DiscardPolicy:丢弃任务,但是不抛出异常。可以配合这种模式进行自定义的处理方式。
  4. DiscardOldestPolicy:丢弃队列最早的未处理任务,然后重新尝试执行任务。

五、自定义一个线程池

@Configuration
public class ExecutorConfig {

    /**
     * 核心线程数:线程池创建时候初始化的线程数
     */
    @Value("${executor.core.pool.size:8}")
    private int corePoolSize;

    /**
     * 最大线程数:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程
     */
    @Value("${executor.max.pool.size:16}")
    private int maxPoolSize;

    /**
     * 缓冲队列200:用来缓冲执行任务的队列
     */
    @Value("${executor.queue.capacity:200}")
    private int queueCapacity;

    /**
     * 允许线程的空闲时间(单位:秒):当超过了核心线程出之外的线程在空闲时间到达之后会被销毁
     */
    @Value("${executor.keepalive.Seconds:60}")
    private int keepAliveSeconds;

    /**
     * 线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池
     */
    @Value("${executor.thread.iothub.prefix:weather-jdtq}")
    private String threadIotNamePrefix;

    /**
     * 线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池
     */
    @Value("${executor.thread.task.prefix:task-executor}")
    private String taskExecutorPrefix;

    ThreadFactory factory = (Runnable r) -> {
        Thread t = new Thread(r);
            t.setDefaultUncaughtExceptionHandler((Thread thread1, Throwable e) -> {
            logger.error("factory的exceptionHandler捕捉到异常--->>> \n" + e.getMessage());
        });
        return t;
    };


    @Bean("hubExecutor")
    public Executor hubExecutor() {

        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(corePoolSize);
        executor.setMaxPoolSize(maxPoolSize);
        executor.setQueueCapacity(queueCapacity);
        executor.setKeepAliveSeconds(keepAliveSeconds);
        executor.setThreadNamePrefix(threadIotNamePrefix);
        executor.setThreadFactory(factory);
        // 线程池对拒绝任务的处理策略:这里采用了CallerRunsPolicy策略,当线程池没有处理能力的时候,该策略会直接在 execute 方法的调用线程中运行被拒绝的任务;如果执行程序已关闭,则会丢弃该任务
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        //TtlExecutors封装,避免线程池执行时租户id传递丢失
        return TtlExecutors.getTtlExecutorService(executor.getThreadPoolExecutor());
    }
}