自己动手实现线程池 jdk线程池ThreadPoolExecutor工作原理解析(一)( 二 )


自己动手实现线程池 jdk线程池ThreadPoolExecutor工作原理解析(一)

文章插图
ThreadPoolExecutor优雅停止线程池的优雅停止一般要能做到以下几点:
  1. 线程池在中止后不能再受理新的任务
  2. 线程池中止的过程中,已经提交的现存任务不能丢失(等待剩余任务执行完再关闭或者能够把剩余的任务吐出来还给用户)
  3. 线程池最终关闭前,确保创建的所有工作线程都已退出 , 不会出现资源的泄露
线程池自启动后便会有大量的工作线程在内部持续不断并发的执行提交的各种任务,而要想做到优雅停止并不是一件容易的事情 。因此ThreadPoolExecutor中最复杂、细节最多的部分并不在于上文中的正常工作流程 , 而在于分散在各个地方但又紧密协作的,控制优雅停止的逻辑 。
ThreadPoolExecutor的其它功能除了正常的工作流程以及优雅停止的功能外 , ThreadPoolExecutor还提供了一些比较好用的功能
  1. 提供了很多protected修饰的钩子函数,便于用户继承并实现自己的线程池时进行一定的拓展
  2. 在运行时统计了总共执行的任务数等关键指标,并提供了对应的api便于用户在运行时观察运行状态
  3. 允许在线程池运行过程中动态修改关键的配置参数(比如corePoolSize等) , 并实时的生效 。
jdk线程池ThreadPoolExecutor源码解析(自己动手实现线程池v1版本)如费曼所说:What I can not create I do not understand(我不能理解我创造不了的东西) 。通过模仿jdk的ThreadPoolExecutor实现,从零开始实现一个线程池,可以迫使自己去仔细的捋清楚jdk线程池中设计的各种细节 , 加深理解而达到更好的学习效果 。
前面提到ThreadPoolExecutor的核心逻辑主要分为两部分,一是正常运行时处理提交的任务的逻辑,二是实现优雅停止的逻辑 。因此我们实现的线程池MyThreadPoolExecutor(以My开头用于区分)也会分为两个版本,v1版本只实现前一部分即正常运行时执行任务的逻辑,将有关线程池优雅停止的逻辑全部去除 。相比直接啃jdk最终实现的源码,v1版本的实现会更简单更易理解 , 让正常执行任务时的逻辑更加清晰而不会耦合太多关于优雅停止的逻辑 。
线程池关键成员变量介绍ThreadPoolExecutor中有许多的成员变量,大致可以分为三类 。
可由用户自定义的、用于控制线程池运行的配置参数
  1. volatile int corePoolSize(最大核心线程数量)
  2. volatile int maximumPoolSize(最大线程数量)
  3. volatile long keepAliveTime(idle线程保活时间)
  4. final BlockingQueue workQueue(工作队列(阻塞队列))
  5. volatile ThreadFactory threadFactory(工作线程工厂)
  6. volatile RejectedExecutionHandler handler(拒绝异常处理器)
  7. volatile boolean allowCoreThreadTimeOut(是否允许核心线程在idle超时后退出)
其中前6个配置参数都可以在ThreadPoolExecutor的构造函数中指定,而allowCoreThreadTimeOut则可以通过暴露的public方法allowCoreThreadTimeOut来动态的设置 。其中大部分属性都是volatile修饰的,目的是让运行过程中可以用过提供的public方法动态修改这些值后 , 线程池中的工作线程或提交任务的用户线程能及时的感知到变化(线程间的可见性) , 并进行响应(比如令核心线程自动的idle退出)这些配置属性具体如何控制线程池行为的原理都会在下面的源码解析中展开介绍 。理解这些参数的工作原理后才能在实际的业务中使用线程池时为其设置合适的值 。
仅供线程池内部工作时使用的属性
  1. ReentrantLock mainLock(用于控制各种临界区逻辑的并发)
  2. HashSet workers(当前活跃工作线程Worker的集合,工作线程的工作原理会在下文介绍)
  3. AtomicInteger ctl(线程池控制状态 , control的简写)
这里重点介绍一下ctl属性 。ctl虽然是一个32位的整型字段(AtomicInteger),但实际上却用于标识两个业务属性,即当前线程池的运行状态和worker线程的总数量 。在线程池初始化时状态位RUNNING,worker线程数量位0(private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));) 。ctl的32位中的高3位用于标识线程池当前的状态,剩余的29位用于标识线程池中worker线程的数量(因此理论上ThreadPoolExecutor最大可容纳的线程数并不是231-1(32位中符号要占一位),而是229-1)由于聚合之后单独的读写某一个属性不是很方便,所以ThreadPoolExecutor中提供了很多基于位运算的辅助函数来简化这些逻辑 。
ctl这样聚合的设计比起拆分成两个独立的字段有什么好处?在ThreadPoolExecutor中关于优雅停止的逻辑中有很多地方是需要同时判断当前工作线程数量与线程池状态后,再对线程池状态工作线程数量进行更新的(具体逻辑在下一篇v2版本的博客中展开) 。且为了执行效率,不使用互斥锁而是通过cas重试的方法来解决并发更新的问题 。而对一个AtomicInteger属性做cas重试的更新,要比同时控制两个属性进行cas的更新要简单很多,执行效率也高很多 。

推荐阅读