基本原理

线程池基本常识

线程池(Thread Pool)是一种基于池化思想管理线程的工具。线程频繁的创建、销毁会产生大量的系统内核调用,消耗CPU资源用线程池来维护多个线程的生命周期,一方面可以避免线程频繁地创建销毁,另一方面也可以解决线程的调度管理问题

线程池带来一系列好处

  • 降低资源消耗:池化技术重复利用已创建的线程,从而降低线程创建和销毁造成的损耗
  • 提高响应速度:任务到达时,如果有空闲线程,则任务无须再等待线程创建
  • 线程可管理:线程交由线程池统一管理,避免线程无限制创建造成资源损耗,及一些线程分布不合理造成的资源调度失衡。
  • 更强大功能:面向开发人员更加灵活强大的操作。如定时执行或者延时执行。

池化思想在很多领域都有广泛应用,其他几种比较典型的使用策略:

  • 内存池
  • 连接池
  • 实例池

线程池核心设计和实现


上图是一个简略的类继承图。

线程池基本思想是:将任务提交和任务执行解耦。用户只需要提供一个runnable对象,然后将任务交给执行器Executor,无需关注线程如何创建,执行过程。

ExecutorService则为执行器增加了一些能力:

  • 扩充执行任务的能力,补充可以为一个或者一批异步任务生成Future的方法
  • 提供了控制线程池的方法,例如停止线程池执行。

AbstractExcutorService 则是抽象实现类,将执行任务的流程封装起来,保证下层实现时能够简单且正确

ThreadPoolExecutor的运行如下:
任务管理部分主要负责线程的流转

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

线程管理主要负责线程分配及回收。


线程池自身生命周期

线程池运行的状态,是由线程池内部维护的线程池内部使用一个变量维护两个值:运行状态(3bit)(runState)和线程数量(workerCount)(29bit)

线程池中定义的运行状态有5种:

线程池运行状态转换:

任务管理

任务调度

任务的调度都由execute方法完成,这部分完成的工作包括:检查线程池的运行状态、线程运行数、运行策略,从而决定接下来的流程,是直接申请线程,还是放到缓冲队列,亦或者直接拒绝该任务。执行流程如下:

  • 1.确定线程池是RUNNING状态,否则直接拒绝。
  • 2.正在运行的线程数 < corePoolSize,直接创建并启动一个新线程执行任务。
  • 3.正在运行的线程数 >=corePoolSize,且线程池内阻塞队列未满,则任务放入队列。
  • 4.阻塞队列已满,正在运行线程>=corePoolSize && 正在运行线程数 < maximumPoolSize,创建启动线程执行任务。
  • 5.线程池阻塞队列已满, 正在运行的线程数 >= maximumPoolSize,根据拒绝策略处理该任务,默认方式是抛出异常。

任务调度流程图:


任务缓冲

线程池本质是对任务和线程的管理,做到这一点最关键的思想就是任务和线程解耦。线程池采用生产者消费者模式,通过阻塞队列实现。阻塞队列缓存任务,工作线程从阻塞队列获取任务

BlockingQueue(阻塞队列):当队列为空时,消费线程会等待队列非空;当队列满时,生产线程会等待队列可用。

任务申请

当工作线程空闲后,会尝试获取线程,获取过程中会做如下判断:
  • 1.线程池是否已经停止运行,如果是,则返回null
  • 2.线程数现阶段是否过多,如果超出设置数量,会返回null
  • 3.线程如果一直获取不到任务,就会被回收掉,从而保证线程数量处在一个可控范围内。

核心方法如下:

任务拒绝

任务拒绝是线程池的保护策略,线程池达到最大容量(缓存队列已满且线程数达到设置最大值),会对任务进行拒绝。拒绝策略是一个接口。

public interface RejectedExecutionHandler{void rejectedExecution(Runnabler,ThreadPoolExecutor executor)
}

用户可以自定义拒绝策略,或者采用jdk提供的策略

线程管理

worker线程

线程池通过一张Hash表去维护线程的引用,这样可以通过添加引用、移除引用来控制线程的生命周期,worker通过继承AQS来实现独占锁的功能,使用不可重入锁来控制线程的执行状态。


  • 1.lock方法一旦获取了独占锁,就表示线程正在执行任务
  • 2.如果正在执行任务,则线程不应该中断
  • 3.如果线程不是独占锁状态,就说明他是空闲状态,可以中断该线程
  • 4.线程池在执行shutdown方法或tryTerminate方法时会调用interruptIdleWorkers方法来中断空闲的线程,interruptIdleWorkers方法会使用tryLock方法来判断线程池中的线程是否是空闲状态;如果线程是空闲状态则可以安全回收

worker线程增加

addWorker方法增加线程,这个方法里面有两个参数:firstTask、core。firstTask用于指定新增的线程执行的第一个任务,该参数可以为空;core=true表示增加线程是判断当前活动线程数是否少于corePoolSize,core=false表示新增线程需要判断当前活动线程数是否少于maximumPoolSize

worker线程回收

线程回收主要依赖JVM自动回收,线程池做的工作只是维护线程的引用,防止线程被回收,当一些线程需要被回收时,只要删除他的引用即可。Worker被创建出来后,就会不断轮询获取任务执行。当Worker无法获取任务时,就会结束循环,Worker会主动消除自己身上的引用。


worker线程退出

worker线程执行任务

runWorker方法执行任务,执行过程:

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

java提供的几种线程池

线程实现方式

Thread、Runnable、Callable

//实现Runnable接口的类将被Thread执行,表示一个基本任务
public interface Runnable {    //run方法就是它所有内容,就是实际执行的任务    public abstract void run();
}
//Callable同样是任务,与Runnable接口的区别在于它接口泛型,同时它执行任务候带有返回值;
//Callable的使用通过外层封装成Future来使用
public interface Callable<V> {    //相对于run方法,call方法带有返回值    V call() throws Exception;
}

注意:启动Thread线程只能用start(JNI方法)来启动,start方法通知虚拟机,虚拟机通过调用器映射到底层操作系统,通过操作系统来创建线程来执行当前任务的run方法

Executor框架

Executor接口是线程池框架中最基础的部分,定义了一个用于执行Runnable的execute方法。从图中可以看出Exectuor下有一个重要的子接口ExecutorService,其中定义了线程池的具体行为:

execute(Runnable runnable):执行Runnable类型的任务
submit(task):用来提交Callable或者Runnable任务,并返回代表此任务的Future对象
shutdown():在完成已经提交的任务后封闭办事,不在接管新的任务
shutdownNow():停止所有正在履行的任务并封闭办事
isTerminated():是一个钩子函数,测试是否所有任务都履行完毕了
isShutdown():是一个钩子函数,测试是否该ExecutorService是否被关闭

ExecutorService中的重点属性:

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

ctl:对线程池的运行状态和线程池中有效线程的数量进行控制的一个字段,它包含两部分信息:线程池的运行状态(runState)和线程池内有效线程的数量(workerCount)。这里可以看到,使用Integer类型来保存,高3位保存runState,低29位保存workerCount。


COUNT_BITS 就是29,CAPACITY 就是1左移29位减1(29个1),这个常量表示workerCount的上限值,大约是5亿。

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;
}

线程池运行的状态

    RUNNING = -1 << COUNT_BITS; //高3位为111SHUTDOWN = 0 << COUNT_BITS; //高3位为000STOP = 1 << COUNT_BITS; //高3位为001TIDYING = 2 << COUNT_BITS; //高3位为010TERMINATED = 3 << COUNT_BITS; //高3位为011

线程池实例的状态

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

【JVM技术专题】较为深入分析线程池基本原理及实现机制「 入门篇」相关推荐

  1. 【JVM技术专题】深入分析CG管理和原理查缺补漏「番外篇」

    前提概要 本文主要针对 Hotspot VM 中"CMS + ParNew" 组合的一些使用场景进行总结. 自 Sun 发布 Java 语言以来,开始使用GC技术来进行内存自动管理 ...

  2. 【JVM技术专题】针对于ASM库生成和修改class文件开发指南 「 入门篇」

    任何足够先进的科技,都与魔法无异 相信你对Java编译以后的class字节码无论是在文件格式以及元数据方面已经有了很多的认识和了解,接下来我们不停留在理论的基础上,动手去操作和控制class字节码,这 ...

  3. 【JVM技术专题】 深入学习JIT编译器实现机制「 原理篇」

    前提概要 解释器 Java程序最初是通过解释器(Interpreter)进行解释执行的,当虚拟机发现某个方法或代码块的运行特别频繁的时候,就会把这些代码认定为"热点代码"(hots ...

  4. 深入分析线程池的实现原理

    点击上方 好好学java ,选择 星标 公众号 重磅资讯.干货,第一时间送达 今日推荐:SQL 语法速成手册文末点击阅读原文,去B站看视频,别忘记关注哦 个人原创100W+访问量博客:点击前往,查看更 ...

  5. 深入剖析线程池基本原理以及常见面试题详解

    文章目录 面试官:能给我讲讲线程池的实现原理? 线程池类继承关系 ThreadPoolExecutor 核心数据结构 面试官:给我讲讲线程池的有哪些参数? 面试官:如何优雅的关闭线程? 线程的生命周期 ...

  6. 池化技术及jdk的线程池讲解

    概述 程序运行的本质是消耗系统资源,线程.数据库连接等都会耗费系统的资源.线程.数据库连接等的创建.销毁等都十分消耗系统资源,所以,如果使用池化技术(线程池.数据库连接池等),可以对系统资源进行控制和 ...

  7. 京东T7团队技术4面:线程池+索引+Spring +分布式锁+Mysql+项目等

    前言: 金九银十过了金三银四还会远嘛,本文后面分享面试题给正准备跳槽,准备找工作的你,这次面试京东也是做了很多的准备,还好顺利拿到了offer. 面试,其实是一个双向选择的过程,在这个过程里,我们不应 ...

  8. [.Net线程处理系列]专题二:线程池中的工作者线程

    目录: 一.上节补充 二.CLR线程池基础 三.通过线程池的工作者线程实现异步 四.使用委托实现异步 五.任务 六.小结 一.上节补充 对于Thread类还有几个常用方法需要说明的. 1.1 Susp ...

  9. 技术派-windows的线程池APIs

    目录 API接口列表 举例说明 看到许多人用pthread或boost里面的线程池接口或者自己封装线程池,其实我们的微软自带也有对应的线程池API,在Kernel32.dll内部,纯C接口导出,自己用 ...

最新文章

  1. R语言dim函数获取dataframe、matrix的维度、shape实战
  2. yd的拔钉子之路之 POI 2017
  3. Message LongText(消息的详细长文本)
  4. ssm mysql 插入date 数据_SSM中插入数据没有报错,但是数据库没有值?报错-问答-阿里云开发者社区-阿里云...
  5. libcareplus一个Qemu-6.1.0热补丁示例
  6. mfc opengl 三维地形图_衢州三维动画制作传媒企业哪家好2020收费
  7. android多图片拖动,Android实现图片拖动效果
  8. VJ 1490 小菜的数码验证
  9. Java 实战篇-JDK9新特性
  10. 从看《长津湖》想到的数字化转型
  11. 深入浅出Mysql 读书笔记
  12. PageRank算法(Dead ends、Spider Traps问题)
  13. 30个非常有趣的404错误页面设计欣赏
  14. linux串口结构termios,linux串口termios.doc
  15. OMC IT监控运维管理平台建设方案
  16. 视频接口的种类及数据类型
  17. php 虚线怎么画,ps画虚线最详细教程
  18. java图片闪烁_java在窗口中添加图片做动画,怎么一闪一闪的?
  19. matlab元胞数组cell添加元素
  20. git中patch的用法

热门文章

  1. 户用光伏市场乱象丛生 恶性竞争影响发展
  2. Vue使用iconify图标
  3. 美国就业前景最好的专业介绍
  4. Go 1.20要来了,看看都有哪些变化-第1篇
  5. openSUSE 截图快捷键配置
  6. arctime必须要java_下载Arctime字幕软件 | Arctime字幕软件
  7. 报告论文:数字图象处理系列问题研究(小丑图像除噪)
  8. 蓝桥杯 2022 真题 纸张尺寸
  9. CPU和cache之间,cache和主存之间,主存和辅存之间数据交换的单位分别是什么?
  10. 2020中国独角兽报告(附榜单)