作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO
联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬
学习必须往深处挖,挖的越深,基础越扎实!
阶段1、深入多线程
阶段2、深入多线程设计模式
阶段3、深入juc源码解析
码哥源码部分
码哥讲源码-原理源码篇【2024年最新大厂关于线程池使用的场景题】
码哥讲源码-原理源码篇【揭秘join方法的唤醒本质上决定于jvm的底层析构函数】
码哥源码-原理源码篇【Doug Lea为什么要将成员变量赋值给局部变量后再操作?】
码哥讲源码【谁再说Spring不支持多线程事务,你给我抽他!】
打脸系列【020-3小时讲解MESI协议和volatile之间的关系,那些将x86下的验证结果当作最终结果的水货们请闭嘴】
一、ThreadPoolExecutor简介
在juc-executors框架概述的章节中,我们已经简要介绍过ThreadPoolExecutor
了,通过Executors工厂,用户可以创建自己需要的执行器对象。ThreadPoolExecutor,它是J.U.C在JDK1.5时提供的一种实现了ExecutorService接口的执行器,或者说线程池。
ThreadPoolExecutor并没有自己直接实现ExecutorService接口,因为它只是其中一种Executor的实现而已,所以Doug Lea把一些通用部分封装成一个抽象父类—— AbstractExecutorService ,供J.U.C中的其它执行器继承。如果读者需要自己实现一个Executor,也可以继承该抽象类。
1.1 AbstractExecutorService
AbstractExecutorService 提供了 ExecutorService 接口的默认实现——主要实现了 submit、invokeAny 、invokeAll这三类方法,如果读者看过上一篇综述文章,就应该知道,ExecutorService的这三类方法几乎都是返回一个Future对象。而Future是一个接口,AbstractExecutorService既然实现了这些方法,必然要实现该Future接口,我们来看下AbstractExecutorService实现的 submit 方法:
public <T> Future<T> submit(Runnable task, T result) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task, result);
execute(ftask);
return ftask;
}
可以看到,上述方法首先对Runnable和返回值value进行了封装,通过newTaskFor
方法,封装成了一个 FutureTask 对象,然后通过execute方法执行任务,最后返回异步任务对象。
这里其实是模板方法模式的运用,execute是抽象方法,需要由继承AbstractExecutorService的子类来实现。
上述需要注意的是newTaskFor方法,该方法创建了一个Future对象:
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
return new FutureTask<T>(runnable, value);
}
FutureTask其实就是Future接口的实现类:
我们之前讲过,J.U.C中的Future接口是“Future模式”的多线程设计模式的实现,可以让调用方以异步方式获取任务的执行结果。而FutureTask便是这样一类支持异步返回结果的任务,既然是任务就需要实现Runnable接口,同时又要支持异步功能,所以又需要实现Future接口。J.U.C为了方便,新定义了一个接口—— RunnableFuture ,该接口同时继承Runnable和Future,代表支持异步处理的任务,而FutureTask便是它的默认实现。
本节不会在Futrure模式上花费太多笔墨,以后我们会专门讲解J.U.C对Future模式的支持。
1.2 线程池简介
回到ThreadPoolExecutor,从该类的命名也可以看出,这是一种线程池执行器。线程池大家应该并不陌生,应用开发中经常需要用到数据库连接池,数据库连接池里维护着一些数据库连接,当应用需要连接数据库时,并不是自己创建连接,而是从连接池中获取可用连接;当关闭数据库连接时,只是将该连接还给连接池,以供复用。
而线程池也是类似的概念,当有任务需要执行时,线程池会给该任务分配线程,如果当前没有可用线程,一般会将任务放进一个队列中,当有线程可用时,再从队列中取出任务并执行,如下图:
线程池的引入,主要解决以下问题:
- 减少系统因为频繁创建和销毁线程所带来的开销;
- 自动管理线程,对使用方透明,使其可以专注于任务的构建。
二、ThreadPoolExecutor基本原理
了解了线程池和ThreadPoolExecutor的继承体系,接下来,我们来看下J.U.C是如何实现一个普通线程池的。
2.1 构造线程池
我们先来看下ThreadPoolExecutor的构造器,其实之前在讲Executors时已经接触过了,Executors工厂方法创建的三种线程池:newFixedThreadPool、newSingleThreadExecutor、newCachedThreadPool,内部都是通过ThreadPoolExecutor的下面这个构造器实例化了ThreadPoolExecutor对象:
/**
* 使用给定的参数创建ThreadPoolExecutor.
*
* @param corePoolSize 核心线程池中的最大线程数
* @param maximumPoolSize 总线程池中的最大线程数
* @param keepAliveTime 空闲线程的存活时间
* @param unit keepAliveTime的单位
* @param workQueue 任务队列, 保存已经提交但尚未被执行的线程
* @param threadFactory 线程工厂(用于指定如果创建一个线程)
* @param handler 拒绝策略 (当任务太多导致工作队列满时的处理策略)
*/
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException(