前面我们介绍了最简单的任务执行器接口Executor,它仅仅定义一个方法,即void execute(Runnable command)。但实际工程中我们可能需要对任务进行某些控制,或者对任务执行器的生命周期进行管理,此时Executor接口就无法满足要求了。于是ExecutorService接口应运而生,它是Executor接口的加强版。
ExecutorService接口
ExecutorService接口位于java.util.concurrent包下,该接口继承了Executor接口。为了加强对执行器的控制和对有返回值任务的执行,该接口增加了很多新方法,比如关闭执行器的方法、查看执行器状态的方法、提交Callable任务的方法等等。该接口包含的方法较多,这里只列出一些常用的方法,我们主要还是去理解执行器的设计思路。
shutdown方法,用于关闭执行器,调用该方法后停止接收新任务,但已经提交给执行器的任务将继续执行完毕。
isShutdown方法,执行器是否已关闭。
iSTerminated方法,执行器是否已终止。
awaitTermination方法,阻塞等待执行器直到其终止。
submit(Callable)方法,向执行器提交一个具有返回值的任务。
ExecutorService接口
下面我们通过自己实现一个简易的任务执行器来加深对ExecutorService接口的理解,该任务执行器中主要包括任务队列和工作线程,工作线程不断从任务队列取出任务并执行。同时根据ExecutorService接口的定义我们还需要实现执行器的关闭操作及状态查询,此外还需要支持执行具有返回值的任务。
我们定义一个MyExecutorService类,该类实现了ExecutorService接口。先看属性,isShutdown和isTerminated分别表示是否关闭和是否已终止。taskQueue是一个列表结构的任务队列,workers是工作线程,这里创建了5个工作线程,count之所以用了AtomicInteger是为了让执行器能在多线程中正确关闭。lock用于实现线程阻塞通知功能,因为awaitTermination方法要阻塞到执行器终止。接着看构造函数,我们会在构造函数中创建工作线程并启动它们,启动后的线程不断从任务队列中取出任务并执行,其中会根据isShutdown标识决定是否要跳出循环,跳出循环则意味着工作线程死亡结束。由于我们希望在关闭任务管理器时能将原来在任务队列中的队列执行完,所以还加了个taskQueue.isEmpty()作为判断条件。
构造函数与属性
接着看各个方法的实现,shutdown直接将isShutdown置为true,结合execute方法来看,当isShutdown为true时则任务不会继续被添加到任务队列中了,而且工作线程也会在任务队列为空时死亡。再看awaitTermination方法,它只需直接进入阻塞即可,在任务执行器终止时会通过termination条件唤醒阻塞的线程。submit方法将传入的Callable对象封装成FutureTask对象,然后添加到任务队列中,最后返回Future对象。
所有方法
例子一
根据我们实现的MyE
{!-- PGC_COLUMN --}
xecutorService类来编写第一个例子,MyTask对象睡眠一秒钟,以下为输出结果,由于执行器中只有5个工作线程,所以最多只能五个任务并发执行。主线程启动了另外一个线程在睡眠10秒后对执行器进行shutdown操作,主线程调用awaitTermination方法后开始阻塞,直到执行器终止后主线程才能继续往下。
输出结果
例子一
例子二
第二个例子展示具有可返回值的任务,MyTask需要实现Callable接口,然后在call方法中定义任务。将任务提交给执行器后调用Future的get方法将使主线程进入阻塞状态,直到任务执行完毕返回结果,最终输出receive result : task_result。
例子二
本专栏的所有代码都会同步更新到我的github上,需要的朋友可以去下载,这里禁止发地址,那就截个图吧。另外,麻烦各位能给专栏给个好评,在此谢谢各位了。
跟着作者的 70节课彻底搞懂Java并发原理 专栏,一步步彻底搞懂Java并发原理。
作者简介:笔名seaboat,擅长人工智能、计算机科学、数学原理、基础算法。出版书籍:《图解数据结构与算法》、《Tomcat内核设计剖析》、《人工智能原理科普》。
作者的三个专栏:
更多Java并发原理内容敬请关注 70节课彻底搞懂Java并发原理 专栏!
更多Java并发原理内容敬请关注 40节课彻底搞懂数据结构与算法 专栏!
更多Java并发原理内容敬请关注 人工智能原理科普 专栏!