深入源码分析Java线程池的实现原理
程序的运行,其本质上,是对系统资源(CPU、内存、磁盘、网络等等)的使用。如何高效的使用这些资源是我们编程优化演进的一个方向。今天说的线程池就是一种对CPU利用的优化手段。
通过学习线程池原理,明白所有池化技术的基本设计思路。遇到其他相似问题可以解决。
池化技术
前面提到一个名词——池化技术,那么到底什么是池化技术呢?
池化技术简单点来说,就是提前保存大量的资源,以备不时之需。在机器资源有限的情况下,使用池化技术可以大大的提高资源的利用率,提升性能等。
在编程领域,比较典型的池化技术有:
线程池、连接池、内存池、对象池等。
本文主要来介绍一下其中比较简单的线程池的实现原理,希望读者们可以举一反三,通过对线程池的理解,学习并掌握所有编程中池化技术的底层原理。
创建一个线程
在Java的并发编程中,线程是十分重要的,在Java中,创建一个线程比较简单:
- public class App {
- public static void main(String[] args) throws Exception {
- new Thread(new Runnable() {
- @Override
- public void run() {
- System.out.println("线程运行中");
- }
- }).start();
- }
- }
我们通过创建一个线程对象,并且实现Runnable接口就可以实现一个简单的线程。可以利用上多核CPU。当一个任务结束,当前线程就接收。
但很多时候,我们不止会执行一个任务。如果每次都是如此的创建线程->执行任务->销毁线程,会造成很大的性能开销。
那能否一个线程创建后,执行完一个任务后,又去执行另一个任务,而不是销毁。这就是线程池。
这也就是池化技术的思想,通过预先创建好多个线程,放在池中,这样可以在需要使用线程的时候直接获取,避免多次重复创建、销毁带来的开销。
线程池的简单使用
以下代码,是在Java中创建线程池:
- import java.util.concurrent.*;
- public class App {
- public static void main(String[] args) throws Exception {
- ExecutorService executorService = new ThreadPoolExecutor(1, 1,
- 60L, TimeUnit.SECONDS,
- new ArrayBlockingQueue<>(10));
- executorService.execute(new Runnable() {
- @Override
- public void run() {
- System.out.println("abcdefg");
- }
- });
- executorService.shutdown();
- }
- }
Jdk提供给外部的接口也很简单。直接调用ThreadPoolExecutor构造一个就可以了,也可以通过Executors静态工厂构建,但一般不建议。
可以看到,开发者想要在代码中使用线程池还是比较简单的,这得益于Java给我们封装好的一系列API。但是,这些API的背后是什么呢,让我们来揭开这个迷雾,看清线程池的本质。
线程池构造函数
通常,一般构造函数会反映出这个工具或这个对象的数据存储结构。
构造函数
如果把线程池比作一个公司。公司会有正式员工处理正常业务,如果工作量大的话,会雇佣外包人员来工作。
闲时就可以释放外包人员以减少公司管理开销。一个公司因为成本关系,雇佣的人员始终是有最大数。
如果这时候还有任务处理不过来,就走需求池排任务。
- acc : 获取调用上下文
- corePoolSize: 核心线程数量,可以类比正式员工数量,常驻线程数量。
- maximumPoolSize: 最大的线程数量,公司最多雇佣员工数量。常驻+临时线程数量。
- workQueue:多余任务等待队列,再多的人都处理不过来了,需要等着,在这个地方等。
- keepAliveTime:非核心线程空闲时间,就是外包人员等了多久,如果还没有活干,解雇了。
- threadFactory: 创建线程的工厂,在这个地方可以统一处理创建的线程的属性。每个公司对员工的要求不一样,恩,在这里设置员工的属性。
- handler:线程池拒绝策略,什么意思呢?就是当任务实在是太多,人也不够,需求池也排满了,还有任务咋办?默认是不处理,抛出异常告诉任务提交者,我这忙不过来了。
添加一个任务
接着,我们看一下线程池中比较重要的execute方法,该方法用于向线程池中添加一个任务。
源码
核心模块用红框标记了。
- 第一个红框:workerCountOf方法根据ctl的低29位,得到线程池的当前线程数,如果线程数小于corePoolSize,则执行addWorker方法创建新的线程执行任务;
- 第二个红框:判断线程池是否在运行,如果在,任务队列是否允许插入,插入成功再次验证线程池是否运行,如果不在运行,移除插入的任务,然后抛出拒绝策略。如果在运行,没有线程了,就启用一个线程。
- 第三个红框:如果添加非核心线程失败,就直接拒绝了。
这里逻辑稍微有点复杂,画了个流程图仅供参考
接下来,我们看看如何添加一个工作线程的?
添加worker线程
从方法execute的实现可以看出:addWorker主要负责创建新的线程并执行任务,代码如下(这里代码有点长,没关系,也是分块的,总共有5个关键的代码块):
- 第一个红框:做是否能够添加工作线程条件过滤:
判断线程池的状态,如果线程池的状态值大于或等SHUTDOWN,则不处理提交的任务,直接返回;
- 第二个红框:做自旋,更新创建线程数量:
通过参数core判断当前需要创建的线程是否为核心线程,如果core为true,且当前线程数小于corePoolSize,则跳出循环,开始创建新的线程
有人或许会疑问 retry 是什么?这个是java中的goto语法。只能运用在break和continue后面。
接着看后面的代码:
- 第一个红框:获取线程池主锁。
线程池的工作线程通过Woker类实现,通过ReentrantLock锁保证线程安全。
- 第二个红框:添加线程到workers中(线程池中)。
- 第三个红框:启动新建的线程。
接下来,我们看看workers是什么。
一个hashSet。所以,线程池底层的存储结构其实就是一个HashSet。
worker线程处理队列任务
- 第一个红框:是否是第一次执行任务,或者从队列中可以获取到任务。
- 第二个红框:获取到任务后,执行任务开始前操作钩子。
- 第三个红框:执行任务。
- 第四个红框:执行任务后钩子。
这两个钩子(beforeExecute,afterExecute)允许我们自己继承线程池,做任务执行前后处理。
到这里,源代码分析到此为止。接下来做一下简单的总结。
总结
所谓线程池本质是一个hashSet。多余的任务会放在阻塞队列中。
只有当阻塞队列满了后,才会触发非核心线程的创建。所以非核心线程只是临时过来打杂的。直到空闲了,然后自己关闭了。
线程池提供了两个钩子(beforeExecute,afterExecute)给我们,我们继承线程池,在执行任务前后做一些事情。
线程池原理关键技术:锁(lock,cas)、阻塞队列、hashSet(资源池)
最后希望对你理解线程池有帮助。最后,留一个思考题,为什么线程池的底层数据接口采用HashSet来实现?
深入源码分析Java线程池的实现原理的更多相关文章
- 源码分析—ThreadPoolExecutor线程池三大问题及改进方案
前言 在一次聚会中,我和一个腾讯大佬聊起了池化技术,提及到java的线程池实现问题,我说这个我懂啊,然后巴拉巴拉说了一大堆,然后腾讯大佬问我说,那你知道线程池有什么缺陷吗?我顿时哑口无言,甘拜下风,所 ...
- SOFA 源码分析 — 自定义线程池原理
前言 在 SOFA-RPC 的官方介绍里,介绍了自定义线程池,可以为指定服务设置一个独立的业务线程池,和 SOFARPC 自身的业务线程池是隔离的.多个服务可以共用一个独立的线程池. API使用方式如 ...
- netty源码分析 - Recycler 对象池的设计
目录 一.为什么需要对象池 二.使用姿势 2.1 同线程创建回收对象 2.2 异线程创建回收对象 三.数据结构 3.1 物理数据结构图 3.2 逻辑数据结构图(重要) 四.源码分析 4.2.同线程获取 ...
- Memcached源码分析之线程模型
作者:Calix 一)模型分析 memcached到底是如何处理我们的网络连接的? memcached通过epoll(使用libevent,下面具体再讲)实现异步的服务器,但仍然使用多线程,主要有两种 ...
- Spring Ioc源码分析系列--@Autowired注解的实现原理
Spring Ioc源码分析系列--@Autowired注解的实现原理 前言 前面系列文章分析了一把Spring Ioc的源码,是不是云里雾里,感觉并没有跟实际开发搭上半毛钱关系?看了一遍下来,对我的 ...
- 深入源码,深度解析Java 线程池的实现原理
java 系统的运行归根到底是程序的运行,程序的运行归根到底是代码的执行,代码的执行归根到底是虚拟机的执行,虚拟机的执行其实就是操作系统的线程在执行,并且会占用一定的系统资源,如CPU.内存.磁盘.网 ...
- Java线程池应用及原理分析(JDK1.8)
目录 一.线程池优点 二.线程池创建 三.任务处理流程 四.任务缓存队列及排队策略 五.任务拒绝策略 六.线程池关闭 七.线程池实现原理 八.静态方法创建线程池 九.如何确定线程池大小 一.线程池优点 ...
- Netty源码解析一——线程池模型之线程池NioEventLoopGroup
本文基础是需要有Netty的使用经验,如果没有编码经验,可以参考官网给的例子:https://netty.io/wiki/user-guide-for-4.x.html.另外本文也是针对的是Netty ...
- 【高并发】通过ThreadPoolExecutor类的源码深度解析线程池执行任务的核心流程
核心逻辑概述 ThreadPoolExecutor是Java线程池中最核心的类之一,它能够保证线程池按照正常的业务逻辑执行任务,并通过原子方式更新线程池每个阶段的状态. ThreadPoolExecu ...
随机推荐
- CentOS7 修改主机名
命令: hostnamectl set-hostname [yourhostname] 不过这种方法,大写会自动变成小写. 还有一种方法,直接修改 /etc/hostname文件,这个可以保证大写不变 ...
- 纪念品分组(NOIP2007)
纪念品分组(NOIP2007)[题目描述] 元旦快到了,校学生会让乐乐负责新年晚会的纪念品发放工作. 为使得参加晚会的同学所获得的纪念品价值相对均衡,他要把 购来的纪念品根据价格进行分组,但每组最多只 ...
- [解决]java.lang.IllegalArgumentException: Bad level "DEBUG"
Tomcat启动报错,搞得烦的一比.常规思维就会迷瞪,谁让tomcat的日志级别特殊ne.... http://tomcat.apache.org/tomcat-7.0-doc/ 错误现象: Hand ...
- 转:Ogre的MaterialSystem分析
1. Mesh .SubMesh.SubEntity和Entity 所有的Mesh对象是由SubMesh构成的,每个SubMesh代表了Mesh对象的一部分,该部分只能使用一种Meterial.如果一 ...
- jsp页面如何读取从后台传来的json
===================================问====================================== var obj = jQuery.parseJSO ...
- openstack网络基础
一.概述 网络虚拟化是云计算的最重要部分,本文详细讲述 Linux 抽象出来的各种网络设备的原理.用法.数据流向.您通过此文,能够知道如何使用 Linux 的基础网络设备进行配置以达到特定的目的,分析 ...
- 【HTML】如何判断当前浏览器是否是IE
HTML里: HTML代码中,在编写网页代码时,各种浏览器的兼容性是个必须考虑的问题,有些时候无法找到适合所有浏览器的写法,就只能写根据浏览器种类区别的代码,这时就要用到判断代码了.在HTML代码中, ...
- 【struts2】名为chain的ResultType
1)基本使用 名称为“chain”的ResultType,在struts-default.xml里的配置如下: <result-type name="chain" class ...
- 【js】在js中加HTML注释标签的原因?
<script type="text/JavaScript"> <!-- js代码 //--> //就是这句,为什么还要在-->前加上js注释 < ...
- MongodDB---初识
NoSQL介绍 一.NoSQL简介 NoSQL,全称是”Not Only Sql”,指的是非关系型的数据库. 非关系型数据库主要有这些特点:非关系型的.分布式的.开源的.水平可扩展的. 原始的目的是为 ...