本章主要对Java并发包的线程池实现Executor框架的用法和源码实现进行介绍,包括线程池实现ThreadPoolExecutor,异步执行结果Future,任务周期性执行ScheduledExecutorService以及ForkJoin递归任务分解与并行执行框架。
在第3章Java多线程基础的学习中,我们介绍了Java语言提供Runnable接口来实现线程执行和任务定义的分离。不过在使用的时候,还是需要在应用程序中显式定义Thread类对象实例并调用其start方法来启动线程,这样Runnable任务才能得到执行。除此之外,这种机制没有提供线程池机制来实现线程对象的复用,导致需要为每个Runnable任务都创建一个Thread类对象实例。
为了简化Java多线程的使用,在JDK 1.5中提供了Executor线程执行器框架或者说是线程池框架。通过Executor接口的相关实现类来定义一个线程池,然后只需要实现Runnable接口定义自己的任务即可,不需要再显式创建Thread对象和调用start方法来启动线程执行,取而代之的是交给Executor定义的线程池,在线程池内部自动调度线程来执行。
除此之外,为了实现任务的周期性执行,Executor线程池框架的继承体系也存在周期性调度执行线程池的实现,用户可以指定任务周期性执行的频率,之后线程池会自动根据这个频率来周期性地执行这个任务。
在设计与实现层面,Executor线程池框架会在内部维护一个Thread线程池,当有任务提交时,在内部自动调度线程池中的一个空闲线程来执行该任务。同时由于需要执行的任务的数量可能多于线程池的线程数量,所以在内部也会维护一个任务等待队列(工作队列)来存放暂时得不到执行的任务。当有空闲线程时,该线程会从该任务等待队列获取任务并执行。
由于Executor线程池框架是基于内部的Thread线程池和任务等待队列来实现任务的执行的,所以提交给Executor线程池框架执行的任务是异步执行。同时由于是异步执行,所以对于任务的执行结果,Executor线程池框架可以为每次的任务提交,返回一个Future对象给应用程序。应用程序可以通过该Future对象来阻塞等待该任务的执行结果,或者在任务执行之前,通过该Future对象来取消任务的执行。
总的来说,Executor线程池框架除了可以实现任务的提交和任务的执行分离,简化Java多线程编程的复杂度,还具有以下优点。
(1)通过线程池机制,实现线程复用,避免频繁地进行Thread线程对象的创建和销毁,提高了在执行大量任务时的性能,也避免了之前那种对于每个任务都需要显式创建一个Thread线程对象来执行的问题。
(2)通过设置内部的Thread线程池和任务等待队列的大小,可以根据当前系统资源情况,灵活控制系统的最大资源开销,避免创建过多线程或者在任务等待队列存放过多任务,造成系统资源的过多消耗,如内存、CPU资源使用过度导致系统宕机。
(3)Executor线程池框架也提供了审计功能,即提供了任务执行情况的统计功能,如当前的Executor线程池实例一共执行了多少个任务。
关于Executor线程池框架的使用方法和设计原理,接下来以自下而上的顺序详细分析,以便从设计者的角度来学习。