[并发编程] - Executor框架#ThreadPoolExecutor源码解读01

2021-08-17 15:45:17 浏览数 (1)


Pre

Java-Java中的线程池原理分析及使用


Thread

线程是调度CPU资源的最小单位,线程模型分为KLT模型与ULT模型。

Java线程与OS线程

JVM使用的KLT模型,Java线程与OS线程保持1:1的映射关系,也就是说有一个java线程也会在操作系统里有一个对应的线程 。

[并发编程] - 操作系统底层工作原理

使用new Thread 创建500个线程

代码语言:javascript复制
 public static void main(String[] args) {
        for (int i = 0; i < 500; i  ) {
            new Thread(() -> {
                while (true){
                    try {
                        TimeUnit.MILLISECONDS.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }

观察OS的线程数量的增长情况 。

,停止后,再观察其回落的状况

验证了 JVM使用的KLT模型,Java线程与OS线程保持1:1的映射关系。


生命状态

  • NEW 新建
  • RUNNABLE 运行
  • BLOCKED 阻塞
  • WAITING 等待
  • TIMED_WAITING 超时等待
  • TERMINATED 终结

状态切换


线程池

why

[并发编程] - 操作系统底层工作原理 中 【CPU运行安全等级】部分中说明了从用户态切换到内核态实际上是一个非常重型的操作。

如果并发的请求数量非常多,但每个线程执行的时间很短,这样就会频繁的创建和销毁线程,如此一来会大大降低系统的效率 ,可能出现服务器在为每个请求创建新线程和销毁线程上花费的时间和消耗的系统资源要比处理实际的用户请求的时间和资源更多。

线程池为线程生命周期的开销和资源不足问题提供了解决方案。通过对多个任务重用线程,线程创建的开销被分摊到了多个任务上。


use case

  • 单个任务处理时间比较短
  • 需要处理的任务数量很大

Advantage

  • 重用存在的线程,减少线程创建,消亡的开销,提高性能
  • 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行
  • 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资 源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控

Executor框架

Executor接口是线程池框架中最基础的部分,定义了一个用于执行Runnable的execute方法。

比较常见的几个类的关系如下

Executor接口定义了唯一的接口方法

代码语言:javascript复制
void execute(Runnable command);

Executor下有一个重要子接口ExecutorService,其中定义了线程池的具体行为

代码语言:javascript复制
ExecutorService extends Executor
  • submit(task):可用来提交Callable或Runnable任务,并返回代表此任务的Future 对象
  • shutdown():在完成已提交的任务后封闭办事,不再接管新任务,
  • shutdownNow():停止所有正在履行的任务并封闭办事。
  • isTerminated():测试是否所有任务都履行完毕了。
  • isShutdown():测试是否该ExecutorService已被关闭。

ThreadPoolExecutor 源码分析

ThreadPoolExecutor 继承 AbstractExecutorService ,而 AbstractExecutorService 实现了ExecutorService 接口。

高三位低29位

在Java中,一个int占据32位, 使用了Integer类型来保存,高3位保存runState,低29位保存workerCount

代码语言:javascript复制
  private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
  
  // 用多少二进制位表示线程数量
  private static final int COUNT_BITS = Integer.SIZE - 3;
  
  // 线程最大数量 COUNT_BITS 就是29,CAPACITY就是1左移29位减1(29个1) 约5亿
  private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

先看下这个ctl , 通过 ctlOf(RUNNING, 0) 可以知道 ctlOf这个方法中包含的两个参数信息 : 线程池的运行状态 runState 线程池中有效线程的数量 workerCount 。

COUNT_BITS : 29

代码语言:javascript复制
// 高三位表示 线程池状态
// runState is stored in the high-order bits
private static final int RUNNING    = -1 << COUNT_BITS;

RUNNING 表示线程池处于 运行状态,COUNT_BITS 是 29,因此这个位运算就表示 -1 左移 29 位。

-1 如用 2 进制表示

获取 -1 的正数,也就是 1 的二进制: 0000000…00000 1 (前面 31 位 0)

对上一步进行取反, 1111111111…1111 0 (前面 31 位 1)

对上一步 1 操作, 111111111…1111 (32 位 1)

因此 - 1 左移 29 位, 就得到了 111 0000…00000 ( 29个 0) 。 高三位 111 表示 RUNNING 状态

同理

代码语言:javascript复制
// 高三位为 000 
private static final int SHUTDOWN   =  0 << COUNT_BITS;
// 高三位为 001
private static final int STOP       =  1 << COUNT_BITS;
// 高三位为 010
private static final int TIDYING    =  2 << COUNT_BITS;
// 高三位为 011
private static final int TERMINATED =  3 << COUNT_BITS;

ctl相关方法

代码语言:javascript复制
 // Packing and unpacking ctl
 
// 获取运行状态
 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种状态

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

  • SHUTDOWN 线程池处在SHUTDOWN状态时,不接收新任务,但能处理已添加的任务 调用线程池的shutdown()接口时,线程池由RUNNING -> SHUTDOWN

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

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

  • TERMINATED

线程池彻底终止,就变成TERMINATED状态。

线程池处在TIDYING状态时,执行完terminated()之后,就会由 TIDYING > TERMINATED。

进入TERMINATED的条件如下:

  • 线程池不是RUNNING状态;
  • 线程池状态不是TIDYING状态或TERMINATED状态;
  • 如果线程池状态是SHUTDOWN并且workerQueue为空;
  • workerCount为0;
  • 设置TIDYING状态成功

0 人点赞