自定义线程池理论知识部分

Posted 爱叨叨的程序狗

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了自定义线程池理论知识部分相关的知识,希望对你有一定的参考价值。

一、线程池是什么

线程池是一种基于池化思想管理线程的工具。

创建/销毁是一个耗时操作,频繁使用会降低整体性能,使用线程池维护多个线程,可有效降低运行中性能开销,以及更好的管理线程。

Java中自定义线程池是自JDK1.5后出现的ThreadPoolExecutor。 Executor提供了一种思想:将任务提交和任务执行进行解耦,在编码时只需将要执行耗时操作逻辑放入Runanble中即可,无需关心线程调度和执行。

在这里插入图片描述

ExecutorService提供了扩充执行任务的能力,在执行线程内操作时可以获取执行后的返回值Future,同时提供了线程池管控能力。

图2 ThreadPoolExecutor运行流程

线程池内部实际上构建了一个生产者、消费者模型,将线程和任务解耦,并不直接关联,从而复用线程。

线程池的组成

  1. 任务管理:(生产者角色)
  2. 线程管理:(消费者角色)

当提交任务后所经步骤:

  1. 直接申请线程执行该任务
  2. 缓冲到队列中等待线程执行
  3. 拒绝该任务

线程被统一维护在线程池内,根据任务请求进行线程分配,当线程执行完任务后会继续获取新任务执行,最终当线程获取不到任务的时候,线程就会被回收。

线程池生命周期

ThreadPoolExecutor几个重要参数:

corePoolSize:线程池的基本大小,即在没有任务需要执行的时候线程池的大小,并且只有在工作队列满了的情况下才会创建超出这个数量的线程。

maximumPoolSize:线程池中允许的最大线程数,线程池中的当前线程数不会超过该值。

线程池生命周期是由内部隐式维护,线程池的运行状态(runState)和有效线程数量(workerCount)关乎线程池的运行状态。

poolSize:线程池中当前线程的数量,其值为0的时候,意味着没有任何线程,线程池终止。

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

主池控制状态ctl是一个原子整数,封装了两个概念字段workerCount:表示有效线程数runState:表示是否正在运行、正在关闭等,高3位保存runState,低29位保存workerCount

runState 提供主要的生命周期控制,取值:

  • RUNNING:接受新任务并处理排队任务
  • SHUTDOWN:关闭状态,不接受新任务,但处理排队任务
  • STOP:不接受新任务,不处理排队任务,并中断正在进行的任务
  • TIDYING:所有任务都已终止
  • TERMINATED:terminate() 已完成

当workerCount 为零时,转换到状态 TIDYING 的线程将运行 terminate() 钩子方法。

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; }   //通过状态和线程数生成ctl

runState 随时间递增,但不需要命中每个状态。 转换是: RUNNING -> SHUTDOWN 在调用 shutdown() 时,可能隐含在 finalize() 中(RUNNING 或 SHUTDOWN)-> STOP 在调用 shutdownNow() 时 SHUTDOWN -> TIDYING 当队列和池都为空时 STOP -> TIDYING当池为空时 TIDYING -> TERMINATED 当 terminate() 钩子方法完成时,在 awaitTermination() 中等待的线程将在状态达到 TERMINATED 时返回。 检测从 SHUTDOWN 到 TIDYING 的转换,因为在非空之后队列可能会变空,在 SHUTDOWN 状态期间反之亦然,但是我们只能在看到它为空后看到 workerCount 时才终止是 0

生命周期流程

图3 线程池生命周期

任务执行机制

任务调度是线程池的主要入口,用户提交了一个任务,如何执行任务都是由这个阶段决定的。

所有任务的调度都是由execute()方法完成的。

执行流程:

执行流程需要完成的任务:检查现在线程池的运行状态、运行线程数、运行策略、申请线程运行/缓冲到队列/拒绝该任务。

  1. 检测线程池运行状态,如果不是RUNNING,则直接拒绝
  2. workerCount < corePoolSize,创建并启动一个线程来执行新提交的任务
  3. workerCount >= corePoolSize且线程池内的阻塞队列未满,则将任务添加到阻塞队列中,等候处理workQueue.offer(command);
  4. workerCount >= corePoolSize && workerCount < maximumPoolSize && 线程池内的阻塞队列已满,则创建并启动一个线程来执行新提交的任务
  5. workerCount >= maximumPoolSize,并且线程池内的阻塞队列已满,则根据拒绝策略处理该任务,默认处理的方式是直接了抛出异常。

图4 任务调度流程

任务缓冲

任务缓冲模块是线程池能够管理任务的核心部分,线程池的本质是对任务和线程的管理,而做到这一点最关键的思想就是将任务和线程两者解耦,不让两者直接关联,才可以做后续的分配工作。线程池中是以生产者消费者模式,通过一个阻塞队列来实现的。阻塞队列缓存任务,工作线程从阻塞队列中获取任务。

任务申请

两种情况

  1. 直接由新创建的线程执行
  2. 线程从任务队列中获取任务然后执行,执行完任务的线程会再次去队列中申请任务再去执行。

图6 获取任务流程图

任务拒绝

作用是保护线程池,任务队列已满、线程池中的线程数据达到maximumPoolSize时(线程池最大容量)会使用拒绝策略。

public interface RejectedExecutionHandler {
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}

img

Worker线程管理

线程池使用工作线程Worker维护线程的生命周期。Worker实现了Runnable接口,并持有一个线程thread,一个初始化的任务firstTask。

Worker线程增加

增加线程是通过线程池的addWorker方法,该方法不考虑在哪个阶段增加的该线程,其功能仅仅是是增加一个线程,并返回是否成功。

图9 申请线程执行流程图

Worker线程回收

线程池中的销毁依赖JVM的自动回收,线程池根据当前状态维护一定数量的线程引用,防止被JVM回收,当决定回收时,将其引用消除即可,Worker被创建后会不断轮询,核心线程可以无限等待获取任务,非核心线程即时获取任务,Worker无法获取到任务,循环结束,Worker主动消除线程池种的引用。

try {
  while (task != null || (task = getTask()) != null) {
    //执行任务
  }
} finally {
  processWorkerExit(w, completedAbruptly);//获取不到任务时,主动回收自己
}

图10 线程销毁流程

Worker线程执行任务

Worker类中的run方法调用了runWorker方法来执行任务。

1.while循环不断地通过getTask()方法获取任务。 2.getTask()方法从阻塞队列中取任务。 3.如果线程池正在停止,那么要保证当前线程是中断状态,否则要保证当前线程不是中断状态。 4.执行任务。 5.如果getTask结果为null则跳出循环,执行processWorkerExit()方法,销毁线程。

图11 执行任务流程

以上是关于自定义线程池理论知识部分的主要内容,如果未能解决你的问题,请参考以下文章

python---基础知识回顾进程和线程(自定义线程池,上下文管理器和协程的使用)

ThreadPoolExecutor 线程池理论饱和策略工作队列排队策略

Spring使用ThreadPoolTaskExecutor自定义线程池及实现异步调用

池化技术——自定义线程池

JDBC自定义连接池

python---爬虫相关性能(各个异步模块的使用,和自定义异步IO模块)