当前位置: 代码迷 >> 综合 >> JDK源码研究(七):ThreadPoolExecutor
  详细解决方案

JDK源码研究(七):ThreadPoolExecutor

热度:56   发布时间:2023-10-14 01:56:17.0

该类就是我们常用的线程池实现类,Executors中构建的几种线程池大部分都是该类的实例,只不过其构造的参数不同而已。

我们可以先来看下他的内部属性。

JDK源码研究(七):ThreadPoolExecutor

上面截图中记录了一个ctl的AtomicInteger对象,一个储存int的原子对象。该对象非常的精妙,使用了高三位来储存线程池的状态,具体的状态如下下面的常量所示,分别表示:

1、RUNNING

(1) 状态说明:线程池处在RUNNING状态时,能够接收新任务,以及对已添加的任务进行处理。
(02) 状态切换:线程池的初始化状态是RUNNING。换句话说,线程池被一旦被创建,就处于RUNNING状态,并且线程池中的任务数为0!

2、 SHUTDOWN

(1) 状态说明:线程池处在SHUTDOWN状态时,不接收新任务,但能处理已添加的任务。
(2) 状态切换:调用线程池的shutdown()接口时,线程池由RUNNING -> SHUTDOWN。

3、STOP

(1) 状态说明:线程池处在STOP状态时,不接收新任务,不处理已添加的任务,并且会中断正在处理的任务。
(2) 状态切换:调用线程池的shutdownNow()接口时,线程池由(RUNNING or SHUTDOWN ) -> STOP。

4、TIDYING

(1) 状态说明:当所有的任务已终止,ctl记录的”任务数量”为0,线程池会变为TIDYING状态。当线程池变为TIDYING状态时,会执行钩子函数terminated()。terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时,进行相应的处理;可以通过重载terminated()函数来实现。
(2) 状态切换:当线程池在SHUTDOWN状态下,阻塞队列为空并且线程池中执行的任务也为空时,就会由 SHUTDOWN -> TIDYING。
当线程池在STOP状态下,线程池中执行的任务为空时,就会由STOP -> TIDYING。

5、 TERMINATED

(1) 状态说明:线程池彻底终止,就变成TERMINATED状态。
(2) 状态切换:线程池处在TIDYING状态时,执行完terminated()之后,就会由 TIDYING -> TERMINATED。

上述状态描述引用 https://blog.csdn.net/l_kanglin/article/details/57411851?utm_source=copy

ctl剩余的低29位则记录线程池当前的有效线程数。如此存储的好处就是,对线程数量和状态的同时更改都是原子性的,不需要添加锁机制来保证其原子性,当然还有个好处就是节省内存(可以忽略不计吧)。

截图中下面的几个方法,分别是runStateOf(int c)就是取当前线程池高3位的状态值。

workerCountOf(int c)就是获取线程池的有效线程数。

ctlOf(int rs, int wc)就是将状态值和线程数合并成一个int值。

而ctl值的初始值就是运行状态和0个有效线程。

JDK源码研究(七):ThreadPoolExecutor

再来看这三个比较重要的属性,workQueue是当线程池达到核心线程数以后,继续添加的任务会先放置到该队列中,该队列又可分为有界队列和无界队列。mainLock为ThreadPoolExecutor内置的锁对象,更新一些内部属性时会通过该锁对象保证线程安全。workers是当前的线程池的有效线程的集合,线程在线程池中被封装成了worker对象存储在该队列中,并且只有持有mainLock锁才能对该对象进行操作。

JDK源码研究(七):ThreadPoolExecutor

threadFactory是一个线程工厂,里面只有一个方法就是newThread(Runable r),生成一个线程对象。可以自定义自己的线程工厂,给生成的线程对象添加统一的线程组和线程名前缀,可以有利于后期的排查问题。handler对象是当线程关闭或者队列满的时候提供的拒绝策略处理。keepAliveTime指的是线程空闲的存活时间,allowCoreThreadTimeOut是指是否允许核心线程超时,corePoolSize指的是核心线程数, maxinumPoolSize指的是最大线程数。最后定义了一个默认的拒绝策略处理器,会直接抛出一个RejectedExecutionException异常。

JDK源码研究(七):ThreadPoolExecutor

再来看看线程池的常用的核心方法,void execute(Runable commond)

该方法中有这么一段注释,大概的意思就是:

    执行以下三个步骤:

       1.如果运行的线程数少于核心线程数,尝试开启一个新的线程并将传入的commond作为他的第一个任务。调用addWorker线程池运行状态和线程数,防止不能添加线程时发生错误的警告,通过返回false。

       2.如果一个任务可以成功的入队,我们仍旧需要双重检查无论我们是否需要添加一个线程(因为存在一些上次检查后死亡的线程)获取该线程池进入该方法后被关闭了,因此,我们重新检查状态,如果停止,则回滚排队;如果没有,则启动新线程。

      3.如果无法将任务排队,则尝试添加新线程。如果失败了,我们知道我们被关闭或饱和,所以拒绝任务。

照着他的注释理解代码其实就非常容易了,可以看到当当前运行的线程数少于核心线程数时,会尝试将任务包装成一个worker进行添加(addWorker方法,稍后我们详细解释该方法)。如果添加失败,重新获取线程池运行状态,判断是否还是运行状态,并将任务添加到任务队列中,如果添加成功还会对状态进行双重检查,如果发现已经不处于运行状态,则会将任务从任务队列中移除并执行拒绝策略。最后尝试开启一个非核心线程来执行该任务,如果已经大于最大线程数则会添加失败,并返回失败策略。