• 【多线程】创建线程池有几种方式


    网上的文章一般会说,创建线程池基本上是2种方式。ThreadPoolExecutor或者Executors。本文就是通过剖析源码,看下它们的实现。

    ThreadPoolExecutor

    ThreadPoolExecutor是一种自定义线程池的方式。使用ThreadPoolExecutor的方式为new ThreadPoolExecutor(…),构造函数中传入需要的几个参数,通过参数自定义线程池。
    其中涉及7个参数,我将构造函数的参数描述粘贴如下:

         * @param corePoolSize the number of threads to keep in the pool, even
         *        if they are idle, unless {@code allowCoreThreadTimeOut} is set
         * @param maximumPoolSize the maximum number of threads to allow in the
         *        pool
         * @param keepAliveTime when the number of threads is greater than
         *        the core, this is the maximum time that excess idle threads
         *        will wait for new tasks before terminating.
         * @param unit the time unit for the {@code keepAliveTime} argument
         * @param workQueue the queue to use for holding tasks before they are
         *        executed.  This queue will hold only the {@code Runnable}
         *        tasks submitted by the {@code execute} method.
         * @param threadFactory the factory to use when the executor
         *        creates a new thread
         * @param handler the handler to use when execution is blocked
         *        because the thread bounds and queue capacities are reached
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    corePoolSize,直接翻译就是核心池大小,也有文章叫他核心线程数[1]。这个参数表示线程池中不会死亡或者说被回收的线程数量。

    maximumPoolSize,最大池大小,也有文章叫他最大线程数[1]。这个参数表示线程最多有多少线程。注意,这个最大线程数量中包括了不会死亡的核心线程,也包括了其他的会死亡的线程(在线程池空闲一段时间后死亡),它由这两者组成。

    keepAliveTime ,生存时间。这个参数就是说当任务完成以后,并且后面没有任务了,此时线程池空闲了,如果此时maximumPoolSize > corePoolSize,说明有一些线程应该在空闲一段时间后死亡。keepAliveTime 就是指定它们空闲多久后死亡。

    unit ,单位,很容易理解,是死亡时间的时间单位。范围为从天到纳秒[1]。

    TimeUnit.DAYS:天
    TimeUnit.HOURS:小时
    TimeUnit.MINUTES:分
    TimeUnit.SECONDS:秒
    TimeUnit.MILLISECONDS:毫秒
    TimeUnit.MICROSECONDS:微妙
    TimeUnit.NANOSECONDS:纳秒
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    workQueue ,任务队列。线程池有一个maximumPoolSize最大线程数,当同一时间任务数量超过最大线程数的数量,线程池暂时无法处理,它会将多出来的任务放入任务队列中,处理完别的任务再从任务队列中取出并处理。

    threadFactory ,线程工厂。创建线程用的工厂类。

    handler ,策略。其实是拒绝处理任务时的策略。直译过来就是当达到线程边界和任务队列满了的时候,导致的线程阻塞时,要执行的策略。共四种策略,默认策略为 AbortPolicy。

    AbortPolicy:拒绝并抛出异常。
    CallerRunsPolicy:使用当前调用的线程来执行此任务。
    DiscardOldestPolicy:抛弃队列头部(最旧)的一个任务,并执行当前任务。
    DiscardPolicy:忽略并抛弃当前任务。
    
    • 1
    • 2
    • 3
    • 4

    通过如上7个参数,即可自定义出自己想要的线程池。ThreadPoolExecutor类继承了AbstractExecutorService类,AbstractExecutorService类实现了ExecutorService接口。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    Executors

    网传另外一种创建线程的方式就是Executors ,Executors 创建线程池共有6种方式,创建了6种不同类型的线程池,我们来逐个看下它们的源码实现。

    FixedThreadPool
    FixedThreadPool,固定线程池。它是创建一个固定数量的线程池,Executors中有两个实现的方法。

        public static ExecutorService newFixedThreadPool(int nThreads) {
            return new ThreadPoolExecutor(nThreads, nThreads,
                                          0L, TimeUnit.MILLISECONDS,
                                          new LinkedBlockingQueue<Runnable>());
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
        public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
            return new ThreadPoolExecutor(nThreads, nThreads,
                                          0L, TimeUnit.MILLISECONDS,
                                          new LinkedBlockingQueue<Runnable>(),
                                          threadFactory);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    从中可以看出,所谓固定线程池,是调用new ThreadPoolExecutor创建一个核心线程数=最大线程数=固定值的这么一个线程池。这种线程池的特性,自然也就是核心线程数=最大线程数,并且线程在空闲时不会被回收。

    CachedThreadPool
    CachedThreadPool,缓存线程池。实现有如下两种:

    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
        public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
            return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                          60L, TimeUnit.SECONDS,
                                          new SynchronousQueue<Runnable>(),
                                          threadFactory);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    从源码中可以看出,所谓的缓存线程池CachedThreadPool,依然是调用new ThreadPoolExecutor,创建一个核心线程数为0,最大线程数为MAX_VALUE = 0x7fffffff的线程池。这样由于没有核心线程,所有的线程在没有任务时均会死亡。

    SingleThreadExecutor
    SingleThreadExecutor,单线程线程池。顾名思义,只有一个线程的线程池。源码如下:

        public static ExecutorService newSingleThreadExecutor() {
            return new FinalizableDelegatedExecutorService
                (new ThreadPoolExecutor(1, 1,
                                        0L, TimeUnit.MILLISECONDS,
                                        new LinkedBlockingQueue<Runnable>()));
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
        public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
            return new FinalizableDelegatedExecutorService
                (new ThreadPoolExecutor(1, 1,
                                        0L, TimeUnit.MILLISECONDS,
                                        new LinkedBlockingQueue<Runnable>(),
                                        threadFactory));
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    如源码所示,核心线程数=最大线程数=1,依然是通过new ThreadPoolExecutor创建的线程池。这个就是所谓的单线程线程池。

    ScheduledThreadPool
    ScheduledThreadPool,定时线程池。它可以定时或者延时的执行任务。原文描述如下:
    在这里插入图片描述
    两种实现源码如下:

        public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
            return new ScheduledThreadPoolExecutor(corePoolSize);
        }
    
    • 1
    • 2
    • 3
    public static ScheduledExecutorService newScheduledThreadPool(
            int corePoolSize, ThreadFactory threadFactory) {
        return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
    }
    
    • 1
    • 2
    • 3
    • 4

    可以看出,通过调用new ScheduledThreadPoolExecutor指定核心线程数或者核心线程数和线程工厂完成构造线程池。而所谓的ScheduledThreadPoolExecutor,其构造函数通过super构造的ThreadPoolExecutor,也就是说,本质上还是ThreadPoolExecutor来构造的。ScheduledThreadPoolExecutor源码如下:

    public class ScheduledThreadPoolExecutor
            extends ThreadPoolExecutor
            implements ScheduledExecutorService 
    
    • 1
    • 2
    • 3
    public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }
    
    • 1
    • 2
    • 3
    • 4

    其定时或者说延时功能看起来是通过将任务队列指定为延迟队列来实现的。`

    SingleThreadScheduledExecutor
    SingleThreadScheduledExecutor,单线程定时线程池。在Excutors中构造函数实现如下

    public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
        return new DelegatedScheduledExecutorService
            (new ScheduledThreadPoolExecutor(1));
    }
    
    • 1
    • 2
    • 3
    • 4
    public static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory) {
        return new DelegatedScheduledExecutorService
            (new ScheduledThreadPoolExecutor(1, threadFactory));
    }
    
    • 1
    • 2
    • 3
    • 4

    所谓的DelegatedScheduledExecutorService,其实是Excutors的静态内部类,源码如下:

    static class DelegatedScheduledExecutorService
            extends DelegatedExecutorService
            implements ScheduledExecutorService {
        private final ScheduledExecutorService e;
        DelegatedScheduledExecutorService(ScheduledExecutorService executor) {
            super(executor);
            e = executor;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    DelegatedScheduledExecutorService的父类DelegatedExecutorService,也是一个静态内部类。源码如下:

        /**
         * A wrapper class that exposes only the ExecutorService methods
         * of an ExecutorService implementation.
         */
        static class DelegatedExecutorService extends AbstractExecutorService {
            private final ExecutorService e;
            DelegatedExecutorService(ExecutorService executor) { e = executor; }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    从中可以看出,所谓的单线程定时线程池,是首先调用定时线程池里用到的new ScheduledThreadPoolExecutor,构造出以后,将其丢入DelegatedScheduledExecutorService的构造函数中,DelegatedScheduledExecutorService调用其父类DelegatedExecutorService的构造函数,也就是将DelegatedExecutorService中的ExecutorService e变量指定为传入的ScheduledThreadPoolExecutor(ScheduledThreadPoolExecutor实现了接口ExecutorService )。

    不过目前还没理解的是为啥不能直接返回new ScheduledThreadPoolExecutor(1),绕了这么一大圈的意义何在呢。。这个后面再思考思考。。。

    WorkStealingPool
    WorkStealingPool,抢占式线程池。源码如下:

    public static ExecutorService newWorkStealingPool(int parallelism) {
        return new ForkJoinPool
            (parallelism,
             ForkJoinPool.defaultForkJoinWorkerThreadFactory,
             null, true);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
        public static ExecutorService newWorkStealingPool() {
            return new ForkJoinPool
                (Runtime.getRuntime().availableProcessors(),
                 ForkJoinPool.defaultForkJoinWorkerThreadFactory,
                 null, true);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    它与前面几个线程池不同的地方在于,它的任务的完成,不是按照workQueue先进先出的顺序来的,它是抢占式的。ForkJoinPool也并非像ScheduledThreadPoolExecutor继承了ThreadPoolExecutor。代码中没有调用ThreadPoolExecutor。
    在这里插入图片描述

    ForkJoinPool是直接继承的AbstractExecutorService。源码如下:

    public class ForkJoinPool extends AbstractExecutorService
    
    • 1

    ForkJoinPool

    由上一部分引出了另一种创建线程池的方式,也就是ForkJoinPool。它是一种基于Fork/Join思想的线程池,思想类似MapReduce。不过由于它需要对任务进行拆分,线程的任务会被另一个线程偷走,导致线程的界限不是很明确,一般来说只有计算密集型任务才会需要它。这个ForkJoinPool后续再聊。

    总结

    经过源码的探究,可以看出,线程池的构造,可以使用new ThreadPoolExecutor来直接构造一个自定义的线程池,或者Executors的方法来构造6种不同的线程池。Executors中的6种方法,其中有5种都是调用new ThreadPoolExecutor来构造线程池,只有抢占式线程池WorkStealingPool是没有调用new ThreadPoolExecutor来实现线程池,而是通过源自JDK1.7的ForkJoinPool来构建线程池。

    也就是说,不管用ThreadPoolExecutor还是Executors,大多数底层都是调用new ThreadPoolExecutor(),通过指定上面说的核心线程数、最大线程数、任务队列等来实现不同的线程池功能。只有抢占式线程池WorkStealingPool不是这样的。它的底层是通过ForkJoinPool来构建的线程池。底层的实现,只有ThreadPoolExecutor和ForkJoinPool

    参考资料
    [1],面试官:线程池有哪几种创建方式,能详细的说下么?

  • 相关阅读:
    Python tkinter: 开发一个目标检测GUI小程序
    Jenkins + Gitee 实现代码自动化构建 (超级详细)
    Angular 怎么封装基础组件?
    第148篇 笔记-DeFi介绍
    C++20 协程的使用
    STM32驱动步进电机
    一分钟学一个 Linux 命令 - cat 和 tail
    如何监控容器或K8s中的OpenSearch
    开源WIFI继电器之方案介绍
    蓝桥杯算法竞赛系列第九章·巧解哈希题,用这3种数据类型足矣
  • 原文地址:https://blog.csdn.net/gengzhihao10/article/details/125841840