优雅地理解线程池源码

2023-11-09

线程池源码

网上的一些博客对线程池的讲解都是逐行解读源码,看起来可能会比较费力,本文从功能角度出发,以整个流程为切入点,省去一些没必要的源码,带你逐层抽丝剥茧,理解线程池设计的精髓所在。

前置知识

几个常量和变量

// ctl是一个int类型的组合变量(32个bit),低29位表示线程池里的线程数,高3位表示线程池的状态
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

// 所有在线程池中的线程都会被放到HashSet进行保存
private final HashSet<Worker> workers = new HashSet<Worker>();

// 阻塞队列
private final BlockingQueue<Runnable> workQueue;

内部类

Worker简而言之就是对工作线程进一步的封装,不管你是核心线程还是非核心

private final class Worker{
  // 指向当前线程
  final Thread thread;
  // 第一个任务,这里提示线程池是懒初始化,后面具体再谈
  Runnable firstTask;
}

线程池的执行过程

通过execute函数我们宏观把握一下整个流程,然后抽丝剥茧逐层递进。这里需要单独说一下addWorker函数,他是用于创建新线程的,他的第二个参数传入一个boolean值,用于区分是核心线程和非核心线程的,后面再深入研究。

下面就是整个流程,没框起来的不用关注
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D97qLxoX-1651998722488)(/Users/mac/Library/Application%20Support/typora-user-images/image-20220508100145720.png)]
这里还需要提一嘴的是,addWorker就是创建新线程,也就是说我们的线程池是懒初始化的,一开始线程池并没有任何线程,直到有需求过来了才开始逐步创建线程

线程如何在线程池中流转

上面提到,当外界调用线程池的excute函数执行我们的任务时,会通过addWorker函数新建一个线程去执行,那么该线程执行完这个任务之后又何去何从呢?下面进行分析。

其实当我们调用addWorker之后,会执行runWorker函数
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t7j8DvwE-1651998722491)(/Users/mac/Library/Application%20Support/typora-user-images/image-20220508105308909.png)]
因此,我们可以知道,我们的线程在线程池中的一个流转过程大概是这样的:首先线程池的设计是一个懒初始化,当我们提交任务的时候执行任务的线程才会被创建出来,当他执行完这第一个任务之后,他就不断地使用getTask函数尝试去阻塞队列里面拿任务进行执行,如果阻塞队列里面没有任务了,那么这个线程会被阻塞挂起,直到阻塞队列有任务了,会唤醒该线程继续去拿任务执行。而线程的退出我会在下文进行讲解。

非核心线程在空闲一段时间后如何回收

我们知道,当阻塞队列满了,而线程数又没有超过最大线程数的时候,新来的任务会开启新的线程来执行,这些线程称之为非核心线程。而当阻塞队列为空,也就是没有新的任务可以执行,而非核心线程闲置一段时间后,是要被回收的,那么这个回收是如何进行的呢?下面对getTask函数进行详细解读,这里是线程池的一个精髓所在

跟着图中的1,2,3,4的顺序看,这样看起来更容易理解
在这里插入图片描述
那么总结一下,关键点在于,getTask是如何从队列中取任务的,当线程数小于等于核心线程数,使用的是阻塞队列的take方法,有任务就取任务,没任务就阻塞。当线程数大于核心线程数,表明有非核心线程参与工作,使用的是阻塞队列的poll方法,传入一个时间参数,有任务直接取任务,没任务的时候不会像take那样一直阻塞等待,而是等keepAliveTime时间后如果还等不到就返回null。而我们知道,如果说getTask返回null,runWorker就不会一直while循环,而是直接退出了,这也就是非核心线程被回收的原理!

如何优雅地关闭线程池——shutdown和shutdownNow

了解关闭线程池之前先了解一下如何关闭一个线程

如何优雅地关闭一个线程

java提供了stop和destroy函数可以强制杀死线程,但是官方是不建议使用了,为什么呢?因为强制杀死线程的话,可能会导致一些资源,比如文件描述符、网络连接等没有正常关闭,浪费系统资源。所以优雅地关闭线程应该是,让线程执行完后自动关闭。

大概可以有两种方式来实现:

1.设置中断标志位
class MyThread extends Thread{
  private boolean stopped = false;
  
  @Override
  public void run(){
    while(!stopped){
      ...
    }
  }
}

我们可以通过外界线程来设置stopped标志来让该线程退出,从而结束。那么这样做有一个坏处,当while循环中有阻塞方法,比如wait,那么该线程可能会永远关闭不了。因此,引入第二种方法interrupt

2.interrupt函数
class MyThread extends Thread{
  private boolean stopped = false;
  
  @Override
  public void run(){
    while(!Thread.currentThread().isInterrupted()){
      ...
    }
  }
}

然后外界调用该线程interrupt方法就可以结束该程序,与第一个方法不同的是,他可以响应InterruptedException,下面几个函数就是这样:

public static native void sleep() throws InterruptedException;
public final void wait() throws InterruptedException;
public final void join() throws InterruptedException;

线程池状态变化

官方给出的五种线程池状态如下:

private static final int RUNNING    = -1 << COUNT_BITS;
private static final int SHUTDOWN   =  0 << COUNT_BITS;
private static final int STOP       =  1 << COUNT_BITS;
private static final int TIDYING    =  2 << COUNT_BITS;
private static final int TERMINATED =  3 << COUNT_BITS;

RUNNING标记为-1,其余都是为正数,从左到右越来越大,不可逆。其中主要是shutdown和shutdownNow两个的区别,这里着重分析一下:

看下面的对比,其实就两处不同,先设置不同的线程状态,然后分别执行各自的中断函数
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3oUeBjfH-1651998722494)(/Users/mac/Library/Application%20Support/typora-user-images/image-20220508160929757.png)]

因此,这里着重分析一下interruptIdleWorkers()和interruptWorkers()

interruptIdleWorkers()

shutdown只是关闭掉那些空闲的线程,阻塞队列里面的任务还是要执行的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pvgB7G7A-1651998722494)(/Users/mac/Library/Application%20Support/typora-user-images/image-20220508161754879.png)]

interruptWorkers()

shutdownNow()是直接关掉所有线程,管你是不是空闲的,所以代码就直接全部interrupt掉

private void interruptWorkers() {
  final ReentrantLock mainLock = this.mainLock;
  mainLock.lock();
  try {
    for (Worker w : workers)
      w.interruptIfStarted();
  } finally {
    mainLock.unlock();
  }
}

从SHUTDOWN或者STOP状态过度到TIDYING状态

上面的shutdown和shutdownNow函数最后都会执行tryTerminate()方法,这里就不贴代码了,直接口述一下:tryTerminate()不会直接关闭线程池,而是判断一下,当工作线程数为0,工作队列为空的时候,就把线程池的状态从SHUTDOWN或者STOP状态变为TIDYING状态,那么TIDYING状态其实就是我们可能需要执行一些钩子函数,比如在线程池关闭之前我们想关闭一些资源。这是他和TERMINATED状态的唯一区别

点赞是出好博客的绝佳动力

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

优雅地理解线程池源码 的相关文章

随机推荐

  • 生成项目目录结构(based on windows system)

    描述 作为程序员 在工作中 我们经常会有需求 需要罗列出项目的结构图 如果手工来整理的话 太过浪费时间 其实我们可以借助tree命令来快速生成目录结构 本文主要介绍一下 基于windows系统 如何快速生成目录结构的方法 步骤 1 开始 g
  • vue项目中点击非刷新按钮,页面刷新并且路由多了个问号解决方案

    问题描述 在vue项目开发过程中 点击查询或重置按钮 结果页面刷新了一遍 发现路径变成了 localhost 8080 advanced 原因 这是因为在 form 表单里 点击了button 按钮 触发了表单的默认事件 也就是触发了提交行
  • 自动化部署MySQL 5.6 步骤 制作到ftp共享,永远使用

    首先需要搭建ftpserver yum install vsftpd service vsftpd start 这样ftp服务就起来了 这里只是简单的使用 所以没有使用配置文件 这样我们只要将需要的文件置于 var ftp pub 文件夹下
  • http包通信方式,通信过程,包解析过程

    HTTP Hypertext Transfer Protocol 是一种客户端 服务器协议 用于在Web上传输数据 HTTP协议定义了客户端和服务器之间的通信方式 通信过程以及数据包解析过程 本文将详细讲解HTTP包通信方式 通信过程和包解
  • Dubbo调用过程监控

    MonitorFilter 主要对调用过程进行监控 public Result invoke Invoker
  • 【代码规范】函数命名总结

    Service DAO 层方法命名规约 Get 完整的拿出某一固定存在的结构 不修改 Build 需要根据一些因素构建一个结构 List 获取批量 Fetch 不用传参数获取 Handle handleFilePathFail这种 用于处理
  • java的windows脱机,Win32 API LogonUser对本地帐户的脱机访问

    是否有一系列标志允许 LogonUser 在计算机未连接到网络时返回可用于冒充本地用户的令牌 但所有帐户已在本地存在 我有域帐户执行应用程序 MYDOMAIN FooUser 而我正试图获得一个假冒令牌 MYLAPTOP TestUser
  • Kubernetes快速入门

    前言 本文旨在为Kubernetes入门使用者 提供使用Kubernetes需要掌握的基本知识 1 基础概念 1 1 集群与节点 kubernetes是一个开源的容器引擎管理平台 实现容器化应用的自动化部署 任务调度 弹性伸缩 负载均衡等功
  • 罗剑锋透视HTTP协议学习笔记---26

    26 信任始于握手 TLS1 2连接过程解析 TLS的几种子协议及结构 一个TLS报文由若干记录层 Record Layer 组成 记录层相当于是一个消息容器 承载其它协议 它包括一个TVL 描述其承载的其它协议 如上图 这是一个Serve
  • git 拉取上游仓库tag并同步

    git remote add upstream https github com xxxx xxxx git git fetch upstream tag vX X git tag git push origin refs tags vX
  • 拦截验证每个请求的权限

    前面做的虽然在界面内看不见没有权限的链接 但可以直接在地址栏输入链接进行访问 所以我们这里要使用拦截器拦截每个访问action的请求 1 struts配置
  • C++ 实现守护进程

    文章目录 1 守护进程概念 1 什么是守护进程 2 守护进程的特点 3 如何查看linux系统中已存在的守护进程 2 守护进程编写的步骤 3 示例 1 守护进程概念 1 什么是守护进程 Linux Deamon守护进程是运行在后台的一种特殊
  • 爬虫高级应用(14. 可见即可爬Selenium)

    本章主要内容 1 安装Selenium和WebDriver 2 Selenium的基本使用方法 3 查找节点 4 节点交互 5 管理Cookie 6 执行JavaScript代码 7 改变节点属性值 Selenium的主要功能 1 打开浏览
  • C++bitset处理二进制位的神器

    初始化 string str1 abc tftf 初始化 bitset lt 20 gt b1 bitset lt 20 gt b2 0xaa bitset lt 20 gt b3 str1 4 4 f t 字符串 起始位置 数量 代表0的
  • 重磅!Cloud Ace 在硅谷设立第一家美国办事处!

    Cloud Ace Corporation 总部位于东京千代田区 Makoto Aoki 总裁 于 2023 年 4 月 12 日成立了一家美国公司 作为其母公司 Yoshidumi Holdings Inc 的子公司 Cloud Ace
  • DBC编辑问题——无法设置报文发送类型和周期

    DBC编辑问题 无法设置报文发送类型和周期 提示 以下为实际CANdb Editor界面 均为置灰状态 无法设置 文章目录 DBC编辑问题 无法设置报文发送类型和周期 前言 一 自定义属性 二 修改发送方式和周期 1 回到message 2
  • 斯坦福密码学课程-笔记-02-Stream Ciphers流密码

    斯坦福密码学课程笔记 02 流密码 Stream Ciphers The One Time Pad Symmetric Ciphers definition The One Time Pad Vernam 1917 Information
  • yml注入map

    记录一次yml文件注入Map 首先是配置类 Component Configuration PropertySource value classpath application yml encoding utf 8 Configuratio
  • tensorflow 模型文件

    我的书 购买链接 京东购买链接 淘宝购买链接 当当购买链接 tensorflow生成的模型文件主要有三个 meta index和 data 分成三个文件的原因是tensorflow将计算图结构和变量值存储在不同的文件里 meta文件描述的是
  • 优雅地理解线程池源码

    线程池源码 网上的一些博客对线程池的讲解都是逐行解读源码 看起来可能会比较费力 本文从功能角度出发 以整个流程为切入点 省去一些没必要的源码 带你逐层抽丝剥茧 理解线程池设计的精髓所在 前置知识 几个常量和变量 ctl是一个int类型的组合