ThreadPoolExecutor源码解析(一)

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 1.ThreadPoolExcuter原理说明  首先我们要知道为什么要使用ThreadPoolExcuter,具体可以看看文档中的说明:   线程池可以解决两个不同问题:由于减少了每个任务的调用开销,在执行大量的异步任务时,它通常能够提供更好的性能,并且还可以提供绑定和管理资源(包括执行集合任务时使用的线程)的方法。

1.ThreadPoolExcuter原理说明
  首先我们要知道为什么要使用ThreadPoolExcuter,具体可以看看文档中的说明:
  线程池可以解决两个不同问题:由于减少了每个任务的调用开销,在执行大量的异步任务时,它通常能够提供更好的性能,并且还可以提供绑定和管理资源(包括执行集合任务时使用的线程)的方法。每个 ThreadPoolExecutor还维护着一些基本的统计数据,如完成的任务数。
  线程池做的其实可以看得很简单,其实就是把你提交的任务(task)进行调度管理运行,但这个调度的过程以及其中的状态控制是比较复杂的。

2.初始化参数介绍
可以直接看最完整的ThreadPoolExcuter的初始化函数:

public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
...
}

逐个介绍如下:
corePoolSize:核心线程数,在ThreadPoolExcutor中有一个与它相关的配置:allowCoreThreadTimeOut(默认为false),当allowCoreThreadTimeOut为false时,核心线程会一直存活,哪怕是一直空闲着。而当allowCoreThreadTimeOut为true时核心线程空闲时间超过keepAliveTime时会被回收。

maximumPoolSize:最大线程数,线程池能容纳的最大线程数,当线程池中的线程达到最大时,此时添加任务将会采用拒绝策略,默认的拒绝策略是抛出一个运行时错误(RejectedExecutionException)。值得一提的是,当初始化时用的工作队列为LinkedBlockingDeque时,这个值将无效。

keepAliveTime:存活时间,当非核心空闲超过这个时间将被回收,同时空闲核心线程是否回收受allowCoreThreadTimeOut影响。

unit:keepAliveTime的单位。

workQueue:任务队列,常用有三种队列,即SynchronousQueue,LinkedBlockingDeque(无界队列),ArrayBlockingQueue(有界队列)。

threadFactory:线程工厂,ThreadFactory是一个接口,用来创建worker。通过线程工厂可以对线程的一些属性进行定制。默认直接新建线程。

RejectedExecutionHandler:也是一个接口,只有一个方法,当线程池中的资源已经全部使用,添加新线程被拒绝时,会调用RejectedExecutionHandler的rejectedExecution法。
默认是抛出一个运行时异常。

  这么多参数看起来好像很复杂,所以Java贴心得为我们准备了便捷的API,即可以直接用Executors创建各种线程池。分别是:

        //创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
//通过设置corePoolSize为0,而maximumPoolSize为Integer.Max_VALUE(Int型数据最大值)实现。
ExecutorService cache = Executors.newCachedThreadPool();
//创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
//通过将corePoolSize和maximumPoolSize的值设置为一样的值来实现。 ExecutorService fixed
= Executors.newFixedThreadPool(num);
//创建一个定长线程池,支持定时及周期性任务执行。
//通过将队列参数workQueue设置为DelayWorkQueue来实现。 ExecutorService schedule
= Executors.newScheduledThreadPool(5);
//创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
//通过将corePoolSize和maximumPoolSize都设置为1来实现。 ExecutorService single
= Executors.newSingleThreadExecutor();

   这几个API会根据具体的情况而使用预设定好默认的初始化参数去创建一个ThreadPoolExecutor。

  这里需要做一个额外说明,在ThreadPoolExcuter中,worker和task是有区别的,task是用户提交的任务,而worker则是用来执行task的线程。在初始化参数中,corePoolSize和maximumPoolSize都是针对worker的,而workQueue是用来存放task的。

3.worker介绍

  前面有介绍了一下worker和task的区别,其中task是用户提交的线程任务,而worker则是ThreadPoolExecutor自己内部实现的一个类了。

  具体源码如下:

  

    /**
     * Woker主要维护着运行task的worker的中断控制信息,以及其他小记录。这个类拓展AbstractQueuedSynchronizer
     * 而来简化获取和释放每一个任务执行中的锁。这可以防止中断那些打算唤醒正在等待其他线程任务的任务,而不是
     * 中断正在运行的任务。我们实现一个简单的不可重入锁而不是ReentrantLo,因为我们不想当其调用setCorePoolSize
     * 这样的方法的时候能获得锁。
     */
    //worker主要是对进行中的任务进行中断控制,顺带着对其他行为进行记录
    private final class Worker
            extends AbstractQueuedSynchronizer
            implements Runnable
    {
        /**
         * This class will never be serialized, but we provide a
         * serialVersionUID to suppress a javac warning.
         */
        private static final long serialVersionUID = 6138294804551838833L;

        /** Thread this worker is running in.  Null if factory fails. */
        //正在跑的线程,如果是null标识factory失败
        final Thread thread;
        /** Initial task to run.  Possibly null. */
        //初始化一个任务以运行
        Runnable firstTask;
        /** Per-thread task counter */
        //每个线程计数
        volatile long completedTasks;

        /**
         * Creates with given first task and thread from ThreadFactory.
         * @param firstTask the first task (null if none)
         * 用给定的first task和从threadFactory创建
         */
        Worker(Runnable firstTask) {
            setState(-1); // inhibit interrupts until runWorker
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);
        }


        /** Delegates main run loop to outer runWorker  */
        //主要调用了runWorker
        public void run() {
            runWorker(this);
        }

        // Lock methods
        //
        // The value 0 represents the unlocked state.
        // The value 1 represents the locked state.
        //锁方法

        //
        protected boolean isHeldExclusively() {
            return getState() != 0;
        }

        //尝试获取锁
        protected boolean tryAcquire(int unused) {
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }
        //尝试释放锁
        protected boolean tryRelease(int unused) {
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        public void lock()        { acquire(1); }
        public boolean tryLock()  { return tryAcquire(1); }
        public void unlock()      { release(1); }
        public boolean isLocked() { return isHeldExclusively(); }

        void interruptIfStarted() {
            Thread t;
            if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
                }
            }
        }
    }

   Worker其实可以看作高级一点的线程。其中继承AbstractQueuedSynchronizer主要是为了实现锁控制。ThreadPoolExecutor会持有并管理Worker,在Worker中firstTask其实就是存放task的,而thread则是存放当前Worker本身的线程。

其中比较重要的就是run方法了,但这个方法其实又是去调用ThreadPoolExecutor里面的runWorker()方法,具体可以看下一节的介绍。

4.ctl介绍以及运行状态说明
首先需要介绍线程池有五种运行状态:
RUNNING(状态值-1): 接收新任务并处理队列中的任务
SHUTDOWN(状态值0): 不接收新任务但会处理队列中的任务。
STOP(状态值1): 不接收新任务,不处理队列中的任务,并中断正在处理的任务
TIDYING(状态值2): 所有任务已终止,workerCount为0,处于TIDYING状态的线程将调用钩子方法terminated()。
TERMINATED(状态值3): terminated()方法完成。

  然后我们可以看看ThreadPoolExcuter中的ctl这个变量。
ctl是ThreadPoolExcuter中比较有意思的一个实现,它是一个AtomicInteger,这里不对AtomicInteger多做讨论,只要知道可以把它看成有原子性的Integer就够了,其实它具有原子性的原理是使用了CAS的技术,这是一种乐观锁的具体实现。
ThreadPoolExcuter是将两个内部值打包成一个值,即将workerCount和runState(运行状态)这两个值打包在一个ctl中,因为runState有5个值,需要3位,所以有3位表示
runState,而其他29位表示为workerCount。
而运行时要获取其他数据时,只需要对ctl进行拆包即可。具体这部分代码如下:

    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    private static final int COUNT_BITS = Integer.SIZE - 3;
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

    // runState is stored in the high-order bits
    private static final int RUNNING    = -1 << COUNT_BITS;
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
    private static final int STOP       =  1 << COUNT_BITS;
    private static final int TIDYING    =  2 << COUNT_BITS;
    private static final int TERMINATED =  3 << COUNT_BITS;

    // Packing and unpacking ctl  
//拆包ctl,分别获取runState和WorkerCount
private static int runStateOf(int c) { return c & ~CAPACITY; } private static int workerCountOf(int c) { return c & CAPACITY; }
//打包操作
private static int ctlOf(int rs, int wc) { return rs | wc; }

5.拒绝策略

当执行器(Executor)处于终止状态,或者执行器在max threads和工作队列都是有界并且处于饱和的时候,新提交的任务会被拒绝。在任一情况下,执行的任务将调用RejectedExecutionHandler的方法rejectedExecution(Runnable, ThreadPoolExecutor)。有以下四种拒绝策略:

1.默认的是ThreadPoolExecutor.AbortPolicy,在这种策略下,处理器会在拒绝后抛出一个运行异常RejectedExecutionException。

2.在ThreadPoolExecutor.CallerRunsPolicy的策略下,线程会调用它直接的execute来运行这个任务。这种方式提供简单的反馈控制机制来减缓新任务提交的速度。

3.在ThreadPoolExecutor.DiscardPolicy策略下,无法执行的任务将被简单得删除掉。

4.在ThreadPoolExecutor.DiscardOldestPolicy策略下,如果executor没有处于终止状态,在工作队列头的任务将被删除,然后会重新执行(可能会再次失败,这会导致重复这个过程)。

 

总结:本篇初步介绍了ThreadPoolExcuter的基本原理,解决了什么问题。而后说明了ThreadPoolExcuter中的初始化参数,对其中的各个参数做初步介绍。再之后介绍ctl变量的作用,并初步介绍了任务提交失败后的拒绝策略。

 


 


相关文章
|
2月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
87 2
|
11天前
|
存储 设计模式 算法
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。 行为型模式分为: • 模板方法模式 • 策略模式 • 命令模式 • 职责链模式 • 状态模式 • 观察者模式 • 中介者模式 • 迭代器模式 • 访问者模式 • 备忘录模式 • 解释器模式
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
|
11天前
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。 结构型模式分为以下 7 种: • 代理模式 • 适配器模式 • 装饰者模式 • 桥接模式 • 外观模式 • 组合模式 • 享元模式
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
|
11天前
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
创建型模式的主要关注点是“怎样创建对象?”,它的主要特点是"将对象的创建与使用分离”。这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。创建型模式分为5种:单例模式、工厂方法模式抽象工厂式、原型模式、建造者模式。
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
|
2月前
|
缓存 监控 Java
Java线程池提交任务流程底层源码与源码解析
【11月更文挑战第30天】嘿,各位技术爱好者们,今天咱们来聊聊Java线程池提交任务的底层源码与源码解析。作为一个资深的Java开发者,我相信你一定对线程池并不陌生。线程池作为并发编程中的一大利器,其重要性不言而喻。今天,我将以对话的方式,带你一步步深入线程池的奥秘,从概述到功能点,再到背景和业务点,最后到底层原理和示例,让你对线程池有一个全新的认识。
57 12
|
30天前
|
PyTorch Shell API
Ascend Extension for PyTorch的源码解析
本文介绍了Ascend对PyTorch代码的适配过程,包括源码下载、编译步骤及常见问题,详细解析了torch-npu编译后的文件结构和三种实现昇腾NPU算子调用的方式:通过torch的register方式、定义算子方式和API重定向映射方式。这对于开发者理解和使用Ascend平台上的PyTorch具有重要指导意义。
|
11天前
|
安全 搜索推荐 数据挖掘
陪玩系统源码开发流程解析,成品陪玩系统源码的优点
我们自主开发的多客陪玩系统源码,整合了市面上主流陪玩APP功能,支持二次开发。该系统适用于线上游戏陪玩、语音视频聊天、心理咨询等场景,提供用户注册管理、陪玩者资料库、预约匹配、实时通讯、支付结算、安全隐私保护、客户服务及数据分析等功能,打造综合性社交平台。随着互联网技术发展,陪玩系统正成为游戏爱好者的新宠,改变游戏体验并带来新的商业模式。
|
3月前
|
缓存 Java 程序员
Map - LinkedHashSet&Map源码解析
Map - LinkedHashSet&Map源码解析
87 0
|
3月前
|
算法 Java 容器
Map - HashSet & HashMap 源码解析
Map - HashSet & HashMap 源码解析
68 0
|
3月前
|
存储 Java C++
Collection-PriorityQueue源码解析
Collection-PriorityQueue源码解析
73 0

推荐镜像

更多