Java并发——Executor框架原理解析

前言

在上一篇文章《Java并发——线程池原理解析》中,对线程池的原理进行了解析,本质上是对ThreadPoolExecutor的解析。实际上ThreadPoolExecutor只是Executor框架中的一名成员。本篇文章我们将围绕Executor框架继续探讨其他成员。
对于Java语言,线程是非常昂贵的资源,而线程本身即是工作单元也是工作机制,直到JDK1.5,提供了强大的J.U.C包,可以将工作单元与工作机制分离开来。其中,Runnable、Callable为工作单元,而执行机制由Executor框架提供。
基于以上的认知,我们将围绕着工作单元与工作机制这两个特征来对Executor框架展开研究。

Executor框架总览

Executor框架的两级调度模型

在文章Java并发——线程与Java线程中,我们详细阐述了Java线程的实现原理以及其优缺点。基于Executor框架,Java多线程程序可以将应用分解为多个任务,然后通过用户级调度器Executor将这些任务映射为固定的线程,基于Java线程实现原理,这些线程将映射到系统底层的硬件上。这就是Executor框架的两级调度模型,如下:

Executor框架的两级调度模型
通过上图可以看出:Executor实际上就是任务/资源的管理者和调度者。

Executor框架的主要成员及结构

Executor框架主要由3大部分组成:

任务

Executor框架中的任务必须要实现Runnable或者Callable接口。

任务的执行

任务执行的核心接口便是Executor接口以及继承Executor接口的java.util.concurrent.ExecutorService接口。从开发者使用的角度来看,还有两个重量级子类:ThreadPoolExecutor和ScheduledThreadPoolExecutor!

任务异步执行后的结果

任务执行后的结果将会被包装成FutureTask,FutureTask继承了Future接口。

为了更加清晰直观的观察Executor框架,我们借助IDEA生成UML类图如下:

Executor框架的类图

主要成员的介绍

Executor框架的主要成员:ThreadPoolExecutor、ScheduledThreadPoolExecutor、Future接口、Runnable接口、Callable接口和Executors。

ThreadPoolExecutor

它是作用主要是线程池的创建以及使用。在上一篇文章:《Java并发——线程池原理解析》有详细的原理介绍。

ScheduledThreadPoolExecutor

它具备ThreadPoolExecutor的能力,同时实现了SchecuduledExecutorService接口,因此能实现任务周期性的调度,没错,就是定时任务!有点像Timer,但比Timer更强大、更完善、更好用!
如今,在企业级项目中,基于Timer或者ScheduledThreadPoolExecutor来实现定时任务越来越少了,原因在于,它无法满足高可用、高并发、分布式应用场景。比如无法实现任务持久化等。取而代之的是使用第三方中间件或者基于第三方中间件实现分布式定时任务,如XXL-JOB、Elastic-JOB等。但是,用得少不代表没有用,理解其使用原理、实现方法依然是很有必要的!关于ScheduledThreadPoolExecutor的使用和详细介绍,网络上有大量的优质文章可以查阅,故这里不再重复造轮子介绍,有机会可以单独写一篇文章来介绍。

Runnable和Callable接口

Runnable和Callable接口的实现类都可以被ThreadPoolExecutor和ScheduledThreadPoolExecutor执行。区别在于:Runnable不会返回执行结果,而Callable接口会返回执行结果。

Executors工具类可以将Runnable接口转换为Callable接口。此时返回的结果为null!既然返回null,那有何意义?意义在于:借助于Future实现任务的管理与监控。其实通过源码就可以看到虽然submit()方法支持Runnable接口,但实际上底层还是转换为了Callable的。源码在FutureTask中: this.callable = Executors.callable(runnable, result);

Future接口

Future接口和实现此接口的FutureTask表示异步计算的结果。当像Executor提交任务时,Executor会返回一个Future对象。

<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);

我们可以调用Future.get()方法等待阻塞线程等待任务执行完毕并返回执行结果。

Executors工具类

Executors工具类可以让开发者们更加方便的使用Executor框架来实现线程池、并发等技术。比如:快捷创建线程池、Runnable转换为Callable等。

FutureTask详解

FutureTask的状态

FutureTask表示任务异步计算的结果。其本身会基于FutureTask.run()方法会产生3个状态如下:

未启动

当创建了FutureTask,但没有执行run()方法之前,此时处于未启动状态。

已启动

当执行了run()方法并且未执行完毕之前,此时处于已启动状态。

已完成

正常情况:当run()方法正常执行完毕了,此时处于已完成状态;
异常情况:在run()方法执行的过程中,调用了cancel()方法或者run()方法本身抛出异常或者被中断了,此时任务属于非正常结束,FutureTask的状态也是已完成状态。

状态流转

基于以上的介绍,FutureTask状态的流转如下:

状态流转

方法说明
方法名解释
get()在处于未启动、已启动状态时,调用get()方法将阻塞调用的线程。直到返回结果。
cancel()调用cancel(true),如果是在未启动、已启动,将会中断执行任务的线程来试图停止任务;调用cancel(false),如果是未启动状态,则会取消任务,如果是已启动状态,则不作任何事情;当任务处于已完成状态时,调用cancel(…)将会返回false状态。
isCancel()判断任务是否已取消
isDone判断任务是否已完成

FutureTask的使用

基于以上介绍,我们直接上一张图,看看FutureTask的各个方法的使用以及对状态的影响:

FutureTask的使用

FutureTask的原理

在JDK1.8之前,FutureTask是基于AQS实现线程安全的。而JDK1.8之后,FutureTask没有再依赖AQS,而是基于内部类WaitNode和UnSafe类自己实现了一套线程安全的方法。在这里,不再深入的阅读源码了,原因有二:1.Java并发系列文章有大量的源码解析,其思路本质上差不多的;2.FutureTask本身的源码不难理解,和AQS、无阻塞队列的源码相比简直不是一个档次。

总结

行文至此,我们了解了Executor框架的模型,主要成员,实现思路和使用方式。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值