目录
1 池化?
进入主题前,先聊一下“池化”
1.1 什么是池化?
平时说“水池”我们都能想象出大概的样子,地上一个大坑,里面全是水~
那假设大坑里面只有一滴水,它还是“水池”吗? 此处定有杠精本精,说是啊。
为了剧情继续发展下去,收了神通吧,赞且认为它只是个大坑。
那1k滴水、1w滴水、10w滴水 或者到100000000滴水同时注入到大坑呢。显然它变成了水池,我们想象中的样子。
往里放10只鱼,它就是鱼池了。
同理,转换到我们变成思想中来,也有很多被池化的概念,比如:数据库连接池、对象池、A池、B池... ... 线程池等。
提取公因式:(数据库连接、对象、A、B... ...线程)池;
所以,由许多属性相同的单元组成的“池子”,便于资源统一管理,就是“池化”思想。
1.2 为什么需要“池化”?
前面说到了鱼池,假设这个鱼池是某个饭店的养鱼地,老板规定,鱼池每天必须要保证里面有100条活鱼,每天计算烹了多少条,早上一次采购补充进去(假设鱼儿都健康的成长,除了被烹)。
分析一下上面这个场景,假设没有这个鱼池,会是什么样的场景:
A大厨想要烹鱼-A大厨现买鱼-A大厨烹鱼;
A1大厨想要烹鱼-A1大厨现买鱼-A1大厨烹鱼;
... ...
A100大厨想要烹鱼-A100大厨现买鱼-A100大厨烹鱼;
想想都可怕,大厨们纷纷离职~这是个体力活~
有了鱼池后,
(1)人力物力降下来了,大厨们又撤回了辞职信
(2)买鱼的流程节省了,烹鱼速度、上菜速度快了
(3)对鱼来说,统一管理,统一喂食,避免肥瘦不一,影响口感
(4)顾客多了,调整每天采购数,储存更多活鱼
同理,类比到编程思想中,池化带来了哪些优势?
(1)降低资源的开销(创建、销毁)
(2)提升资源获取速度(响应速度)
(3)资源可管理
(4)资源池可扩展
2 线程池
2.1 线程池是什么
线程池是基于池化思想,实现了对线程灵活管控的工具。
2.2 线程池的优势
(1)实现对线程可重复利用,降低创建/销毁线程时的开销;
(2)减少线程创建过程,提升任务处理速度;
(3)实现灵活管理线程生命周期,可通过参数调控,较大程度避免事故产生;
(4)可在线程池基础功能上,开发者人为实现扩展功能;
2.3 线程池解决的问题
(1)线程池通过控制其活跃线程数,降低进程/操作系统的资源投入不确定性,从而提升了系统的稳定性;
(2)避免无穷尽的创建线程,导致系统资源耗尽;
(3)统一维护了线程的创建/销毁过程,降低了创建/销毁线程池的开销;
(4)开发者可以根据实际业务需求,灵活配置线程池的参数;
2.4 ThreadPoolExecutor 类
友情提示:本文基于JDK1.8版本完成内容梳理
Java中线程池的核心实现类是 ThreadPoolExecutor 类,类图如下:
Executor:定义执行器,用户只需要传入Runnable对象,由Executor框架完成线程调度;
ExecutorService:扩展了线程池基本操作;
AbstractExecutorService:给出默认实现;
ThreadPoolExecutor:线程池核心实现,也是Java线程池最复杂的实现;
2.4.1 线程池的状态
状态 | 描述 |
RUNNING | 接受新任务并,且可处理排队中任务 |
SHUTDOWN | 不接受新任务,但可处理排队中任务 |
STOP | 不接受新任务,不能处理排队中任务,且中断正在进行的任务 |
TIDYING | 所有任务已被中止,工作线程数为0时,转到该状态。且运行钩子方法terminated() |
TERMINATED | terminated()方法执行完成 |
状态转换:
RUNNING -> SHUTDOWN
调用shutdown()时,可能在finalize()中隐式地执行
(RUNNING or SHUTDOWN) -> STOP
调用shutdownNow()
SHUTDOWN -> TIDYING
当队列和池都为空
STOP -> TIDYING
当池为空
TIDYING -> TERMINATED
当钩子方法terminated()执行完成
状态转换图:
2.4.2 ctl
线程池的运行状态,是在线程池运行过程中,ctl变量维护的
一个变量表示两种含义,低29位标识线程数,高3位表示线程状态
packing/unpacking ctl 方法如下:
packing
unpacking
2.4.3 ThreadPoolExecutor
(1)ThreadPoolExecutor参数
参数 | 描述 |
int corePoolSize | 除非设置allowCoreThreadTimeOut==true,否则要保留在线程池中的线程数,即使它们是空闲的 |
int maximumPoolSize | 线程池中允许的最大线程数 |
long keepAliveTime | 线程池中线程数大于核心线程数时,多余的空闲线程活跃时间 |
TimeUnit unit | keepAliveTime单位 |
BlockingQueue<Runnable> workQueue | 在执行任务之前用于保存任务的队列 |
ThreadFactory threadFactory | 执行程序创建新线程时使用的工厂 |
RejectedExecutionHandler handler | 由于达到线程池边界和队列容量而阻塞执行时触发的策略 |
(2)线程池初始化
如图
(3)任务调度机制
任务调度入口:execute方法
- 若工作线程数小于核心线程数
- ReetrantLock锁定添加工作线程操作
- CAS操作新增工作线程数
- 判断线程池状态是否是RUNNING,只有RUNNING状态才会接收新任务
- 若工作线程数大于等于核心线程数
- 线程池内阻塞队列未满,则将任务添加到阻塞队列
- 线程池内阻塞队列已满
- 工作线程数小于最大线程数,则创建一个线程执行任务
- 工作线程数大于最大线程数,则触发拒绝策略
过程图:
(4)拒绝策略
策略 | 描述 |
AbortPolicy | 抛出异常 RejectedExecutionException |
DiscardPolicy | 静默丢弃被拒绝的任务 |
DiscardOldestPolicy | 丢弃最老未处理请求,并重新提交被拒绝的任务 |
CallerRunsPolicy | 直接在调用execute方法的线程中运行被拒绝的任务 |
3 阻塞队列
3.1 BlockingQueue
阻塞队列在Queue的基础上,另外支持了在检索元素时等待队列变为非空的操作,以及在存储元素时等待队列中的空间可用的操作。
3.2 生产者-消费者模式与BlockingQueue
(1)生产者消费者模式:简单描述就是生产者向缓冲区中添加元素,消费者从缓冲区中获取元素,从而将生产与消费的动作解耦,将生产者消费者1对1,解放为生产者消费者1对多、多对1、多对多等模型。当然解耦只是其中最重要的特性,当生产和消费速度出现差异时,若生产者同步调用消费者方法时,并发处理能力低下,若使用缓冲区实现解耦,可大大提高生产或消费能力。同时生产和消费速度出现差异时,可以通过动态扩展生产者或消费者实例数的策略,控制生产消费均衡,满足生产环境需求。
本篇对生产者-消费者模式只做概述
(2)生产者消费者模式与BlockingQueue:生产者消费者模式是一种编程思想,而BlockingQueue则是java环境中生产者消费者实现的一味良药,为什么这么说,下面介绍BlockingQueue接口的核心方法。
3.3 BlockingQueue核心方法
BlockingQueue有四种形式,不同的形式对应不同的方法,如下:
throw exception | Special value | Blocks | Times out | |
---|---|---|---|---|
插入 | add(e) | offer(e) | put(e) | offer(e,time,unit) |
删除/获取 | remove(e) | poll() | take() | poll(time,unit) |
检索 | element() | peek() | not applicable | not applicable |
(1)插入
1)add(e):如果可以在不违反容量限制的情况下立即将指定的元素插入到该队列中,成功时返回true,如果当前没有可用空间则抛出IllegalStateException。
当使用容量受限的队列时,通常最好使用offer。
2) offer(e):如果可以在不违反容量限制的情况下立即将指定的元素插入到该队列中,成功时返回true,如果当前没有可用空间则返回false。
当使用容量受限的队列时,这种方法通常更适合于add,它只通过抛出异常而无法插入元素。
3)put(e):将指定的元素插入到此队列中,并在必要时等待空间可用。
4)offer(e,time,unit):将指定的元素插入到该队列中,如果需要,等待指定的等待时间,以便空间可用。如果成功,则为true;如果指定的等待时间在可用空间之前结束,则为false。
(2)删除/获取
1)remove(e):元素将从该队列中删除(如果存在),如果该队列因调用而更改,则返回true。
2)poll():删除并返回该队列的头部,如果该队列为空则返回null。
3)take():删除并返回此队列的头部,如有必要等待,直到某个元素可用。
4)poll(time,unit):删除并返回此队列的头部,若队列为空则等待指定时间后返回。
(3)检索
1)element():检索但不删除此队列的头部。
此方法与peek的不同之处在于,如果该队列为空,它将抛出NoSuchElementException。
2)peek():检索但不删除该队列的头部,如果该队列为空则返回null。
(4)其它
1)drainTo(c):从该队列中删除所有可用元素并将它们添加到给定集合c中,返回转移的元素数量。
2)drainTo(c,maxElements):最多从该队列中删除给定数量的可用元素,并将它们添加到给定集合,返回转移的元素数量。
3.4 BlockingQueue容量
阻塞队列有容量限制。在任何给定的时间,它都有一个remainingCapacity,超过了这个容量,就不能在不阻塞的情况下放置其他元素。没有限制容量大小,则默认容量为Integer.MAX_VALUE。