1.对线程池的理解
1.1 艰辛摸索
看过许多关于线程池的介绍和讲解,看过方腾飞的 《并发编程的艺术》 也看过很多博客关于线程池的讲解,但是总觉得自己理解的不太好,总觉得哪里缺点。后来自己也花时间查看源码,自己去琢磨,多问为什么,结合一些博客的讲解。现在我想把这段时间的成果记录下来,也是为了加深自己的理解吧。
下面所有的源码都是java8 源码。
1.2 线程池的好处
降低性能消耗、提高响应速度:对于应用需要频繁创建线程,而且线程任务都比较简单,比如一些IO任务,线程的生命周期都很短;而线程的创建需要花费一定的CPU时间,所以当任务到来时如果线程已经准备就绪了,而不是重新创建,则会大大提高系统的响应速度。
对线程的集中管理监控:将创建的线程规约在线程池里,则可以对线程的数量和运行状态进行管理并进行监控,可对系统的线程资源进行集中管理。
2.线程池内的一些属性
2.1 线程池参数
看下面的这个线程池的构造器,他有许多参数,这些参数都代表什么意思?
corePoolSize 核心的线程池数量
maximumPoolSize 线程池内最大的线程数量
keepAliveTime 线程存活时间
unit 时间单位 比如 秒 分钟
workQueue 存储任务的阻塞队列
threadFactory 自定义线程工厂
handler 拒绝策略
3.Executors 提供的几种线程池
3.1 newFixedThreadPool
固定线程数的线程池
3.2 newCachedThreadPool
缓冲线程池
3.3 newWorkStealingPool
jdk 1.8 新加入的。创建一个带并行级别的线程池,并行级别决定了同一时刻最多有多少个线程在执行,如不穿如并行级别参数,将默认为当前系统的CPU个数
3.4 newScheduledThreadPool
创建一个定长线程池,支持定时及周期性任务执行。
4. 源码
下面介绍下线程池的源码,由于对写作没有天赋,我就尽我最大努力把源码分析的浅显易懂一些。我介绍下线程池的一些状态属性,然后再从提交一个任务开始跟着源码进行描述一下我对源码的理解。
4.1 线程池状态 和 线程统计
image.png
ctl
先看看线程池中最重要的一个属性 ctl,我觉得是Control的简写,ctl控制着整个线程池的运转。ctl是AtomicInteger类型,线程池利用ctl 的高3位作为记录当前线程池的状态。利用低29位记录线程池中线程数,所以线程池中线程的最大容量为 2^29。
默认值是 1110 0000 0000 0000 0000 0000 0000 0000 = -536 870 912 ;
COUNT_BITS
COUNT_BITS = Integer.SIZE - 3;
COUNT_BITS 的意思是 一个整型数 有29位用于统计线程池内线程数;
RUNNING 运行状态
RUNNING 是线程池的初始状态,是一个int 类型的常量,值为 -1 左移 29 位即为 -536 870 912,线程池 的状态
只有RUNNING 是负数的。
SHUTDOWN
RUNNING --shutDowm()--> SHUTDOWN
RUNNING状态调用shutDowm()函数进入SHUTDOWN状态。是一个int类型的常量 值为0 ;
此时线程池不接收新任务,但能处理已添加的任务。
STOP
(RUNNING or SHUTDOWN) ---shutdownNow()--> STOP
RUNNING状态或者SHUTSOWN状态调用shutDowmNow()函数进入SROP状态。是一个int类型的常量 值为536 870 912 ;
不接收新任务,不处理已添加的任务,并且会中断正在处理的任务。
TIDYING
是一个int类型的常量 值为1 073 741 824 ;
SHUTDOWN -> TIDYING:当线程池内线程数为0并且队列内任务数量为0时
STOP -> TIDYING:当线程池内线程数量为0时
TERMINATED
是一个int类型的常量 值为1 610 612 736 ;
线程池处在TIDYING状态时,执行完terminated()之后,就会由 TIDYING -> TERMINATED。
CAPACITY (线程的最大容量)
1 转移 29位 ,就是 0010 0000 0000 0000 0000 0000 0000 0000,
然后 减1 ,就变成了0001 1111 1111 1111 1111 1111 1111 1111,刚好就是 低29位 为1 ,就是线程内线程的最大容量 。
4.2 execute 执行任务
线程池添加任务流程图.png
在未来的某个时刻执行给定的任务,这个任务会被一个新线程或者一个已经存在的线程执行。
如果任务不能提交执行,那是因为线程池不处于运行状态或者已经到达容量上限。
那么这个任务就会被RejectedExecutionHandler处理。
execute()代码逻辑:
4.3 addWorker
检查是否可以根据当前线程池状态和绑定条件(核心线程或者最大线程)添加一个新的工作线程。
如果可以的话那么调整运行的线程数量。
并且 如果线程成功创建和运行的话,那么firstTask将是新线程的第一个任务被执行。
如果线程池已经关闭或者正在关闭,函数将返回false.
如果线程创建失败,可能的原因是因为线程工厂返回null,或者异常(最可能的异常就是在执行Thread,start()时产生OutOfMemoryErro )。
接着回滚以上操作。
参数:core:如果为true 增加核心线程,false 增加最大线程。
4.3 addWorkerFailed
执行addWorker添加任务失败之后 调用该方法执行回滚操作。
回滚操作主要做了以下三件事情:
4.4 runWorker 执行任务
添加worker 成功之后,worker就开始执行runWorker()了。
这个方法是比较重要的方法,是线程池的核心。是worker 线程 运行循环,重复地从队列中获取任务并执行它们,同时处理以下一些问题:
4.5 getTask() 获取任务
阻塞或者限时的通过getTask() 函数获取任务。不仅仅是获取任务,同时通过是不是返回null控制者worker 线程的退出与否。以下情况会导致函数返回null,导致worker 退出:
4.6 processWorkerExit 处理worker退出
对即将死亡worker 进行清理 和 记账(统计worker执行的任务数)
如果worker突然死亡(执行中有异常) 就需要调整运行的线程数
将worker 从worker线程集合中移除。(除名)
可能会终止线程池
在小于STOP状态前提下,如果由于执行 用户任务异常导致线程退出,创建新线程替代
在小于STOP状态前提下,不允许核心线程退出,如果运行中的线程小于核心线程,创建新线程替代。
4.7 tryTerminate 尝试终止池
1.如果池是shutdown 并且 阻塞队列 为空 并且 工作线程数 为空 转换为终止状态
2.如果 池是 stop 并且 工作线程为空 转换为 终止状态
3.满足终止条件,但是工作线程不为 0 ,终止一个空闲线程
4.8 interruptIdleWorkers
中断可能等待任务的线程。如果队列任务是空的,有的线程会被一直阻塞,调用该方法会中断那些被一直阻塞的线程: