摘要:我们就来看看线程池中那些非常重要的接口和抽象类,深度分析下线程池中是如何将抽象这一思想运用的淋漓尽致的。
本文分享自华为云社区《【高并发】深度解析线程池中那些重要的顶层接口和抽象类》,作者:冰 河。
通过对线程池中接口和抽象类的分析,你会发现,整个线程池设计的是如此的优雅和强大,从线程池的代码设计中,我们学到的不只是代码而已!!
题外话:膜拜Java大神Doug Lea,Java中的并发包正是这位老爷子写的,他是这个世界上对Java影响力最大的一个人。
说起线程池中提供的重要的接口和抽象类,基本上就是如下图所示的接口和类。

接口与类的简单说明:
Executor接口:这个接口也是整个线程池中最顶层的接口,提供了一个无返回值的提交任务的方法。
ExecutorService接口:派生自Executor接口,扩展了很过功能,例如关闭线程池,提交任务并返回结果数据、唤醒线程池中的任务等。
AbstractExecutorService抽象类:派生自ExecutorService接口,实现了几个非常实现的方法,供子类进行调用。
ScheduledExecutorService定时任务接口,派生自ExecutorService接口,拥有ExecutorService接口定义的全部方法,并扩展了定时任务相关的方法。
接下来,我们就分别从源码角度来看下这些接口和抽象类从顶层设计上提供了哪些功能。
Executor接口的源码如下所示。
从源码可以看出,Executor接口非常简单,只提供了一个无返回值的提交任务的execute(Runnable)方法。
由于这个接口过于简单,我们无法得知线程池的执行结果数据,如果我们不再使用线程池,也无法通过Executor接口来关闭线程池。此时,我们就需要ExecutorService接口的支持了。
ExecutorService接口是非定时任务类线程池的核心接口,通过ExecutorService接口能够向线程池中提交任务(支持有返回结果和无返回结果两种方式)、关闭线程池、唤醒线程池中的任务等。ExecutorService接口的源码如下所示。
关于ExecutorService接口中每个方法的含义,直接上述接口源码中的注释即可,这些接口方法都比较简单,我就不一一重复列举描述了。这个接口也是我们在使用非定时任务类的线程池中最常使用的接口。
AbstractExecutorService类是一个抽象类,派生自ExecutorService接口,在其基础上实现了几个比较实用的方法,提供给子类进行调用。我们还是来看下AbstractExecutorService类的源码。
注意:大家可以到java.util.concurrent包下查看完整的AbstractExecutorService类的源码,这里,我将AbstractExecutorService源码进行拆解,详解每个方法的作用。
RunnableFuture类用于获取执行结果,在实际使用时,我们经常使用的是它的子类FutureTask,newTaskFor方法的作用就是将任务封装成FutureTask对象,后续将FutureTask对象提交到线程池。
这个方法是批量执行线程池的任务,最终返回一个结果数据的核心方法,通过源代码的分析,我们可以发现,这个方法只要获取到一个结果数据,就会取消线程池中所有运行的任务,并将结果数据返回。这就好比是很多要进入一个居民小区一样,只要有一个人有门禁卡,门卫就不再检查其他人是否有门禁卡,直接放行。
在上述代码中,我们看到提交任务使用的ExecutorCompletionService对象的submit方法,我们再来看下ExecutorCompletionService类中的submit方法,如下所示。
可以看到,ExecutorCompletionService类中的submit方法本质上调用的还是Executor接口的execute方法。
这两个invokeAny方法本质上都是在调用doInvokeAny方法,在线程池中提交多个任务,只要返回一个结果数据即可。
直接看上面的代码,大家可能有点晕。这里,我举一个例子,我们在使用线程池的时候,可能会启动多个线程去执行各自的任务,比如线程A负责task_a,线程B负责task_b,这样可以大规模提升系统处理任务的速度。如果我们希望其中一个线程执行完成返回结果数据时立即返回,而不需要再让其他线程继续执行任务。此时,就可以使用invokeAny方法。
invokeAll方法同样实现了无超时时间设置和有超时时间设置的逻辑。
无超时时间设置的invokeAll方法总体逻辑为:将所有任务封装成RunnableFuture对象,调用execute方法执行任务,将返回的结果数据添加到futures集合,之后对futures集合进行遍历判断,检测任务是否完成,如果没有完成,则调用get方法阻塞任务,直到返回结果数据,此时会忽略异常。最终在finally代码块中对所有任务是否完成的标识进行判断,如果存在未完成的任务,则取消已经提交的任务。
有超时设置的invokeAll方法总体逻辑与无超时时间设置的invokeAll方法总体逻辑基本相同,只是在两个地方添加了超时的逻辑判断。一个是在添加执行任务时进行超时判断,如果超时,则立刻返回futures集合;另一个是每次对结果数据进行判断时添加了超时处理逻辑。
invokeAll方法中本质上还是调用Executor接口的execute方法来提交任务。
submit方法的逻辑比较简单,就是将任务封装成RunnableFuture对象并提交,执行任务后返回Future结果数据。如下所示。
从源码中可以看出submit方法提交任务时,本质上还是调用的Executor接口的execute方法。
综上所述,在非定时任务类的线程池中提交任务时,本质上都是调用的Executor接口的execute方法。至于调用的是哪个具体实现类的execute方法,我们在后面的文章中深入分析。
ScheduledExecutorService接口派生自ExecutorService接口,继承了ExecutorService接口的所有功能,并提供了定时处理任务的能力,ScheduledExecutorService接口的源代码比较简单,如下所示。
至此,我们分析了线程池体系中重要的顶层接口和抽象类。
通过对这些顶层接口和抽象类的分析,我们需要从中感悟并体会软件开发中的抽象思维,深入理解抽象思维在具体编码中的实现,最终,形成自己的编程思维,运用到实际的项目中,这也是我们能够从源码中所能学到的众多细节之一。这也是高级或资深工程师和架构师必须了解源码细节的原因之一。
点击关注,第一时间了解华为云新鲜技术~