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

文章目录

  • Pre
  • Thread
    • Java线程与OS线程
    • 生命状态
    • 状态切换
  • 线程池
    • why
    • use case
    • Advantage
  • Executor框架
  • ThreadPoolExecutor 源码分析
    • 高三位低29位
    • ctl相关方法
    • 线程池存在5种状态

在这里插入图片描述


Pre

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


Thread

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

Java线程与OS线程

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

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

[并发编程] - Executor框架#ThreadPoolExecutor源码解读01_第1张图片

使用new Thread 创建500个线程

 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的线程数量的增长情况 。

[并发编程] - Executor框架#ThreadPoolExecutor源码解读01_第2张图片

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

[并发编程] - Executor框架#ThreadPoolExecutor源码解读01_第3张图片

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


生命状态

[并发编程] - Executor框架#ThreadPoolExecutor源码解读01_第4张图片

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

状态切换

[并发编程] - Executor框架#ThreadPoolExecutor源码解读01_第5张图片


线程池

why

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

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

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


use case

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

Advantage

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

Executor框架

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

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

[并发编程] - Executor框架#ThreadPoolExecutor源码解读01_第6张图片

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

void execute(Runnable command);

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

ExecutorService extends Executor

[并发编程] - Executor框架#ThreadPoolExecutor源码解读01_第7张图片

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

ThreadPoolExecutor 源码分析

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

高三位低29位

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

  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

[并发编程] - Executor框架#ThreadPoolExecutor源码解读01_第8张图片

// 高三位表示 线程池状态
// 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 状态

同理

// 高三位为 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;

[并发编程] - Executor框架#ThreadPoolExecutor源码解读01_第9张图片


ctl相关方法

 // 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状态成功

[并发编程] - Executor框架#ThreadPoolExecutor源码解读01_第10张图片

你可能感兴趣的:(【J.U.C源码】,线程池源码,多线程,并发编程)