• ThreadPoolExecutor参数以及源码介绍


    1.前言

    在阿里巴巴的《Java 开发手册》中是这样规定线程池的:

    线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor
    的方式,这样的处理方式让写的读者更加明确线程池的运行规则,规避资源耗尽的风险。

    Executors 返回的线程池对象的弊端:

    • FixedThreadPool 和 SingleThreadPool:允许的请求队列长度为
      Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。

    • CachedThreadPool 和 ScheduledThreadPool:允许的创建线程数量为
      Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。

    其实当我们去看 Executors
    的源码会发现,Executors.newFixedThreadPool()、Executors.newSingleThreadExecutor()
    和 Executors.newCachedThreadPool() 等方法的底层都是通过 ThreadPoolExecutor
    实现的

    2.ThreadPoolExecutor的核心参数

    ThreadPoolExecutor
    的核心参数指的是它在构建时需要传递的参数,其构造方法如下所示:

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            // maximumPoolSize 必须大于 0,且必须大于 corePoolSize
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }
    
    

    第 1 个参数:corePoolSize

    表示线程池的常驻核心线程数。如果设置为
    0,则表示在没有任何任务时,销毁线程池;如果大于
    0,即使没有任务时也会保证线程池的线程数量等于此值。但需要注意,此值如果设置的比较小,则会频繁的创建和销毁线程(创建和销毁的原因会在本课时的下半部分讲到);如果设置的比较大,则会浪费系统资源,所以开发者需要根据自己的实际业务来调整此值。

    第 2 个参数:maximumPoolSize

    表示线程池在任务最多时,最大可以创建的线程数。官方规定此值必须大于
    0,也必须大于等于
    corePoolSize,此值只有在任务比较多,且不能存放在任务队列时,才会用到。

    第 3 个参数:keepAliveTime

    表示线程的存活时间,当线程池空闲时并且超过了此时间,多余的线程就会销毁,直到线程池中的线程数量销毁的等于
    corePoolSize 为止,如果 maximumPoolSize 等于
    corePoolSize,那么线程池在空闲的时候也不会销毁任何线程。

    第 4 个参数:unit 表示存活时间的单位,它是配合 keepAliveTime 参数共同使用的。

    第 5 个参数:workQueue

    表示线程池执行的任务队列,当线程池的所有线程都在处理任务时,如果来了新任务就会缓存到此任务队列中排队等待执行。

    第 6 个参数:threadFactory

    表示线程的创建工厂,此参数一般用的比较少,我们通常在创建线程池时不指定此参数,它会使用默认的线程创建工厂的方法来创建线程,源代码如下:

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        // Executors.defaultThreadFactory() 为默认的线程创建工厂
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }
    public static ThreadFactory defaultThreadFactory() {
        return new DefaultThreadFactory();
    }
    // 默认的线程创建工厂,需要实现 ThreadFactory 接口
    static class DefaultThreadFactory implements ThreadFactory {
        private static final AtomicInteger poolNumber = new AtomicInteger(1);
        private final ThreadGroup group;
        private final AtomicInteger threadNumber = new AtomicInteger(1);
        private final String namePrefix;
    
        DefaultThreadFactory() {
            SecurityManager s = System.getSecurityManager();
            group = (s != null) ? s.getThreadGroup() :
                                  Thread.currentThread().getThreadGroup();
            namePrefix = "pool-" +
                          poolNumber.getAndIncrement() +
                         "-thread-";
        }
        // 创建线程
        public Thread newThread(Runnable r) {
            Thread t = new Thread(group, r,
                                  namePrefix + threadNumber.getAndIncrement(),
                                  0);
            if (t.isDaemon()) 
                t.setDaemon(false); // 创建一个非守护线程
            if (t.getPriority() != Thread.NORM_PRIORITY)
                t.setPriority(Thread.NORM_PRIORITY); // 线程优先级设置为默认值
            return t;
        }
    }
    

    我们也可以自定义一个线程工厂,通过实现 ThreadFactory
    接口来完成,这样就可以自定义线程的名称或线程执行的优先级了。

    第 7 个参数:RejectedExecutionHandler

    表示指定线程池的拒绝策略,当线程池的任务已经在缓存队列 workQueue
    中存储满了之后,并且不能创建新的线程来执行此任务时,就会用到此拒绝策略,它属于一种限流保护的机制。

    线程池的工作流程要从它的执行方法 execute() 说起,源码如下:

    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();
        // 当前工作的线程数小于核心线程数
        if (workerCountOf(c) < corePoolSize) {
            // 创建新的线程执行此任务
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        // 检查线程池是否处于运行状态,如果是则把任务添加到队列
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            // 再出检查线程池是否处于运行状态,防止在第一次校验通过后线程池关闭
            // 如果是非运行状态,则将刚加入队列的任务移除
            if (! isRunning(recheck) && remove(command))
                reject(command);
            // 如果线程池的线程数为 0 时(当 corePoolSize 设置为 0 时会发生)
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false); // 新建线程执行任务
        }
        // 核心线程都在忙且队列都已爆满,尝试新启动一个线程执行失败
        else if (!addWorker(command, false)) 
            // 执行拒绝策略
            reject(command);
    }
    

    其中 addWorker(Runnable firstTask, boolean core) 方法的参数说明如下:

    • firstTask,线程应首先运行的任务,如果没有则可以设置为 null;

    • core,判断是否可以创建线程的阀值(最大值),如果等于 true 则表示使用
      corePoolSize 作为阀值,false 则表示使用 maximumPoolSize 作为阀值。

  • 相关阅读:
    在阿里云服务器(ECS)上从零开始搭建nginx服务器
    HTML5和CSS3新特性一览
    【react】---手动封装一个简易版的redux
    【react】---17新增的生命周期
    vue单页面应用刷新网页后vuex的state数据丢失的解决方案
    [VUE]object.defineProperty的基本使用
    JavaScript / 本地存储
    转载--httpclient原理和应用
    关于mybatis mapper.xml中的if判断
    idea maven install时,打包找不到微服务common中公用的包
  • 原文地址:https://www.cnblogs.com/xiaodou00/p/13503857.html
Copyright © 2020-2023  润新知