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

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

20200902001421975.png

Pre

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


Thread

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


Java线程与OS线程


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

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



20200902003414535.png


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


20200902003633499.png


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


2020090200381624.png


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


生命状态


20200902004159224.png



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


状态切换



20200902004430509.png



线程池

why


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


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


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


use case


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


Advantage


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

Executor框架


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

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


20200902011027971.png

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

void execute(Runnable command);


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

ExecutorService extends Executor


20200902011254448.png


  • 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

20200902020610786.png


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


20200902020837544.png

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


20200902021735685.png


相关文章
|
11天前
|
缓存 Java 调度
Java并发编程学习10-任务执行与Executor框架
【4月更文挑战第12天】本篇 重点讲解任务执行和 Executor框架的基础知识
21 4
Java并发编程学习10-任务执行与Executor框架
|
5月前
|
并行计算 算法 Java
Java线程池——Executor框架
Executor 框架是 Java5 之后引进的,在 Java 5 之后,通过 Executor 来启动线程比使用 Thread 的 start 方法更好,除了更易管理,效率更好(用线程池实现,节约开销)。 Executor 框架不仅包括了线程池的管理,还提供了线程工厂、队列以及拒绝策略等,Executor 框架让并发编程变得更加简单。
|
7月前
|
Java
异步编程 - 03 线程池ThreadPoolExecutor原理剖析&源码详解1
异步编程 - 03 线程池ThreadPoolExecutor原理剖析&源码详解
21 0
|
7月前
|
安全 Java
异步编程 - 03 线程池ThreadPoolExecutor原理剖析&源码详解2
异步编程 - 03 线程池ThreadPoolExecutor原理剖析&源码详解2
35 0
|
7月前
|
存储 Java 调度
【 Executor线程池原理与源码】
【 Executor线程池原理与源码】
|
10月前
|
存储 Java C++
ThreadPoolExecutor 线程池执行流程及核心源码分析
ThreadPoolExecutor 线程池执行流程及核心源码分析
64 0
|
12月前
|
存储 监控 Java
[并发编程] - Executor框架#ThreadPoolExecutor源码解读02
[并发编程] - Executor框架#ThreadPoolExecutor源码解读02
89 0
|
12月前
|
Java
[并发编程] - Executor框架#ThreadPoolExecutor源码解读03
[并发编程] - Executor框架#ThreadPoolExecutor源码解读03
44 0
|
12月前
|
存储 缓存 Java
【并发编程】线程池及Executor框架
【并发编程】线程池及Executor框架
|
缓存 Java 调度
JUC系列学习(一):线程池Executor框架及其实现ThreadPoolExecutor
`Executor` 框架将任务的提交与任务的执行**解耦**了。