前言
上篇文章 13分钟聊聊并发包中常用同步组件并手写一个自定义同步组件 聊到并发包中常用的同步组件,并且还手把手实现了自定义的同步组件
本篇文章来聊聊并发包下的另一个核心-线程池
阅读本文大概12分钟
通读本篇文章前先来看看几个问题,看看你是否以及理解线程池
- 什么是池化技术?它有什么特点,哪些场景使用?
- Executor是什么?它的设计思想是什么样的?
- 工作任务有几种?有什么特点?如何适配然后交给Executor的?
- 线程池是如何实现的?有哪些核心参数,该如何配置?工作流程是怎样的?
- 线程池如何优雅的处理异常?如何关闭线程池?
- 处理定时的线程池是如何实现的?
池化技术
线程的创建、销毁都会带来一定的开销
如果当我们需要使用到多线程时再去创建,使用完又去销毁,这样去使用不仅会拉长业务流程,还会增加创建、销毁线程的开销
于是有了池化技术的思想,将线程提前创建出来,放在一个池子(容器)中进行管理
当需要使用时,从池子里拿取一个线程来执行任务,执行完毕后再放回池子
不仅是线程有池化的思想,连接也有池化的思想,也就是连接池
池化技术不仅能复用资源、提高响应,还方便管理
Executor框架
Executor框架是什么?
可以暂时把Executor看成线程池的抽象,它定义如何去执行任务
public interface Executor {
void execute(Runnable command);
}
Executor
将工作任务与线程池进行分离解耦
工作任务被分为两种:无返回结果的Runnable
和有返回结果的Callable
在线程池中允许执行这两种任务,其中它们都是函数式接口,可以使用lambda表达式来实现
有的同学可能会有疑问,上文Executor
框架定义的执行方法不是只允许传入Runnable
任务吗?
那Callable
任务调用哪个方法来执行呢?
Future
接口用来定义获取异步任务的结果,它的实现类常是FutureTask
FutureTask
实现Runnable
的同时,还用字段存储Callable
,在其实现Runnable
时实际上会去执行Callable
任务
线程池在执行Callable
任务时,会将使用FutureTask
将其封装成Runnable
执行(具体源码我们后面再聊),因此Executor
的执行方法入参只有Runnable
FutureTask
相当于适配器,将Callable
转换为Runnable
再进行执行
Executor 定义线程池,而它的重要实现是ThreadPoolExecutor
在ThreadPoolExecutor
的基础上,还有个做定时的线程池ScheduledThreadPoolExecutor
ThreadPoolExecutor
核心参数
ThreadPoolExecutor
主要有七个重要的参数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
- corePoolSize 线程池核心线程数量
- maximumPoolSize 线程池允许创建的最大线程数
- keepAliveTime 超时时间,TimeUnit时间单位:非核心线程空闲后存活的时间
- workQueue 存放等待执行任务的阻塞队列
- threadFactory线程工厂:规定如何创建线程,可以根据业务不同规定 不同的线程组名称
- RejectedExecutionHandler 拒绝策略:当线程不够用,并且阻塞队列爆满时如何拒绝任务的策略
拒绝策略 | 作用 |
---|---|
AbortPolicy 默认 | 抛出异常 |
CallerRunsPolicy | 调用线程来执行任务 |
DiscardPolicy | 不处理,丢弃 |
DiscardOldestPolicy | 丢弃队列中最近一个任务,并立即执行当前任务 |
线程池中除了构造时的核心参数外,还使用内部类Worker
来封装线程和任务,并使用HashSet容器workes
工作队列存储工作线程worker
实现原理
流程图
为了清晰的理解线程池实现原理,我们先用流程图和总结概述原理,最后来看源码实现
- 如果工作线程数量小于核心线程数量,创建线程、加入工作队列、执行任务
- 如果工作线程数量大于等于核心线程数量并且线程池还在运行则尝试将任务加入阻塞队列
- 如果任务加入阻塞队列失败(说明阻塞队列已满),并且工作线程小于最大线程数,则创建线程执行
- 如果阻塞队列已满、并且工作线程数量达到最大线程数量则执行拒绝策略
execute
线程池有两种提交方式execute和submit,其中submit会封装成RunnableFuture最终都来执行execute
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);