线程池
一、线程池
1.1 池化技术
- 池化技术能够减少创建对象的次数,对资源进行复用。主要是针对一些创建和销毁开销比较大的资源,比如线程,连接等。
1.2 线程池优点
- 降低资源的消耗。降低线程创建和销毁的资源消耗;
- 提高响应速度。线程的创建时间为T1,执行时间T2,销毁时间T3,免去T1和T3的时间
- 提高线程的可管理性。省去用户对线程的管理;
二、线程池框架
- JDK线程池框架比较复杂,包括一系列的接口,同时还派生出很多其他接口,比如线程执行类的Runnable,Callable
Future,FutureTask,RunnableFutrue接口。(里面有些严格来说不算线程池,但是线程池本身和线程息息相关,线程又涉及到获取结果的Future相关接口,因此糅合人在一起比较多,这里一并梳理)
2.1 Executor体系
![线程池](https://note.youdao.com/yws/api/personal/file/1F287E2F6B114EBFBC161E9CCEC9EECE?method=download&shareKey=09537867fd8b4e614b3b8c5cf54313de)
接口/类 | 功能 |
---|
Executor | 线程池的顶层接口,只提供了任务执行的方法 |
ExecutorService | Executor子接口。提供了更多线程池相关的管理方法,如池销毁、任务提交、异步任务提交。 |
ScheduledExecutorService | ExecutorService子接口。能够定时或周期执行地执行任务。 |
AbstractExecutorService | 为 ExecutorService 的任务提交方法提供了默认实现。 |
ThreadPoolExecutor | 程池类。继承自AbstractExecutorService,提供不同的线程和任务的调度策略。 |
ForkJoinPool | 一种只允许执行ForkJoinTask任务的线程池。采用Fork-Join的思想递归地拆解任务执行来充分利用资源以提升应用性能。JDK1.7加入的成员 |
ScheduledThreadPoolExecutor | 继承ThreadPoolExecutor并实现ScheduledExecutorService,因此是一种线程池,且是一种可以延迟或周期执行任务的线程池,类似java的Timer。 |
Executors | 工具类。简化创建各种线程池的工具类,用到的比较多的是创建ThreadPoolExecutor的实例 |
2.1 Future体系
- 在使用线程的时候,继承Thread类或者实现Runnable接口,我们都是无法获取到线程的执行结果的。由此引入了Callable和Future来异步执行线程并支持获取
结果。相关类继承关系图如下:
![image](https://note.youdao.com/yws/api/personal/file/49C4288F694546FBAFECD75943F74528?method=download&shareKey=920788e56924cc60310826c537e7a131)
接口/类 | 功能 |
---|
Runnable | 实现线程的接口,Thread类也是该接口的实现类,不支持获取结果 |
Future | 代表异步执行的结果。提供任务结果的获取方法(get),任务的取消方法(cancel) |
RunnableFuture | 继承Runnable和Future的接口。结合了2个接口的功能,既可以作为一个线程执行,又可以获取执行完毕后的结果 |
FutureTask | RunnableFuture的核心实现类。常用于异步执行任务时获取结果,执行的线程对象是一个Callable实现类 |
Callable | 实现线程的接口,支持获取结果。 |
三、线程池核心参数和方法
3.1 参数
参数 | 作用 | 默认值/建议值 |
---|
int corePollSize | 核心线程数量 | 无默认值,参考"5.1合理配置线程池" |
int maxmumPollSoze | 最大线程数量 | 无默认值,参考"5.1合理配置线程池" |
long keepAliveTime | 空闲线程的存活时间,只在线程大于corePollSize才有效 | 无默认值 |
TimeUnit unit | keepAliveTime的时间单位 | 无默认值, |
BlockingQueue workQueue | 任务队列。保存提交的工作任务 | 无默认值, |
ThreadFactory threadFactory | 线程工厂 | DefaultThreadFactory |
RejectedExecutionHandler handler | 饱和拒绝策略 | AbortPolicy(抛出异常,拒绝执行) |
- RejectedExecutionHandler参数可选值:
RejectedExecutionHandler可选值 | 策略 |
---|
AbortPolicy | 默认,抛出异常 |
CallerRunsPolicy | 用调用者所在的线程来执行任务 |
DiscardOldestPolicy | 丢弃阻塞队列里最老的任务(队列里最靠前的任务) |
DiscardPolicy | 直接丢弃当前任务 |
自定义 | 实现RejectedExecutionHandler接口即可 |
3.2 方法
方法名 | 备注 |
---|
execute | 不需要返回值 (Executor顶层接口定义的方法) |
submit | 需要返回值,返回Future接口 (ExecutorService接口定义的方法) |
方法名 | 备注 |
---|
shutDown | 设置线程池状态,只中断没有执行任务的线程 |
shutDownNow | 设置线程池状态,尝试停止正在执行和暂停的线程 |
四、线程池转换状态
- 前面我们给出了线程池关键的参数和几个常用方法,没有描述线程的转换状态。每一个线程都有它的生命周期,线程池管理的对象就是线程,处于池中的线程由线程池来维护和管理线程的状态,这里我们分
析其状态转换以及任务被提交后线程池的处理策略。
4.1 线程状态变化
- 线程池默认在首次提交任务时才会创建线程,也可以调用prestartCoreThread()方法预先创建好corePollSize个线程
4.2 任务处理策略
1.如果线程数量小于corePollSize,那么就启动线程执行该任务;
2.如果线程达到了corePollSize,那么将该任务放进阻塞队列;
3.如果队列已满,当前线程数量小于maxmumPollSoze,那么再启动新的线程执行该任务;
4.如果队列已满,当前线程数量也达到了maxmumPollSoze,那么就使用拒绝策略handler处理该线程
4.3 示意图
- 状态变化示意图。从图中我们可以看到,下面的四个数字就对应了4.2中描述的1-4四个步骤;
![image](https://note.youdao.com/yws/api/personal/file/420F43C4EB344A19AF1A334C5AB0C664?method=download&shareKey=85fed74cac688582a0f39401966e0c2d)
五、线程池的使用
5.1 合理配置线程池
任务类型 | 示例 | 推荐配置 |
---|
Cpu(计算)密集型(使用cpu和内存) | 计算,大数分解,正则计算 | corePollSize推荐:机器核心数+1; |
IO密集型 | 文件读取,网络通信,数据库连接 | corePollSize推荐:2*cpu核心数(IO操作比内存和cpu的速度要满三个数量级以上) |
混合型 | 二者均有 | 尝试拆分为上面的2种的组合(考虑拆分的成本和收益) |
- 计算密集型corePollSize推荐为核心数+1,因为有时候在调度线程时会出现页缺失,此处当前线程需要被挂起,如果线程挂起,那么超过的那一个线程会被调度。如果配置太多,线程切换可能会降低效率,带来线程切
换的开销。 - workQueue: 应该使用有界队列(无界队列可能会导致OOM)
5.2 预定义的线程池
5.2.1 常见线程池
线程池 | 特点 |
---|
FixedThreadPool | 创建固定数量的线程,使用无界队列,适用负载较重的服务器 |
SingleThreadExecutor | 创建单个线程,使用无界队列,可以保证任务顺序执行 |
CacheedThreadPool | 根据需要创建新的线程,执行很多短期异步任务,使用了SynchronousQueue |
WorkStealingPool | 基于ForkJoinPool实现 |
ScheduledThreadPoolExecutor | 需要定期执行周期任务,不建议使用Timer |
5.2.2 创建方法
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newWorkStealingPool(int parallelism) {
return new ForkJoinPool
(parallelism,ForkJoinPool.defaultForkJoinWorkerThreadFactory,null, true);
}
public static ExecutorService newWorkStealingPool() {
return new ForkJoinPool
(Runtime.getRuntime().availableProcessors(),ForkJoinPool.defaultForkJoinWorkerThreadFactory, null, true);
}
- ScheduledThreadPoolExecutor
public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
return new DelegatedScheduledExecutorService(new ScheduledThreadPoolExecutor(1));
}
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
- 注意ScheduledThreadPoolExecutor有几个方法:
方法名称 | 作用 |
---|
schedule | 只执行一次,任务还可以延时执行 |
scheduleAtFixedRate | 提交固定时间间隔的任务,前后任务开始的时间间隔是固定的 |
scheduleWithFixedDelay | 提交固定延时间隔执行的任务,不管任务执行多久,前任务执行完毕后,到后任务开始之间的间隔是固定的 |
六、小结
- 本文主要是做了线程池的框架和常见线程池的介绍,需要对线程本身有一定的基础,后面再给出一些线程池的具体使用场景和部分经典核心源码的解析。
七、参考