Java并发Executor框架
java Executor框架的主要类之间关系图如下图1所示(图片来自java并发编程的艺术):
由上图我们可总结出Executor框架主要由三部分构成:(1)我们需要提交的任务:Runnable、Callable,(2)执行任务的执行框架:上图我们红色标注的地方
,(3)就是异步执行的返回结果:FutureTask、Future。
在第一部分:和Runnable不同的是,Callable是一个泛型接口接口,也只有唯一的call()方法。Callable被线程执行后具有返回值,并且可以抛出异常,而且改返回值可以用future得到。下面是个例子:
其中类FutureTask实现了接口Runnable和Future。因此,在我们上面的例子中,FutureTask可以用来被当作线程执行,也可以用来接收结果。
第二部分:JDK1.5之后引入的Executor框架最大的优点就是将任务的提交和执行解耦。对于客户端而言,只需创建task提交给Executor框架即可,至于task如何
执行、何时执行、何时结束等客户端都不需要过问。经过这样的封装,对于客户端而言,提交任务获取结果的过程大大简化,使得后台线程如何执行任务不为人所知。
图片摘自infoQ:http://www.infoq.com/cn/articles/executor-framework-thread-pool-task-execution-part-01。
参考并发编程的艺术:Executor框架最核心的类是ThreadPoolExecutor,是线程池的实现类。它由四个组件构成:corePool、maximumPool、BlockingQueue<Runnable>(暂时保存任务的工作队列)、RejectedExecutionHandler。
BlockingQueue:暂时保存任务的阻塞队列,可以选择以下4种中的一种
LinkedBlockingQueue:基于链表结构实现的阻塞队列,FIFO,是一种无界队列。
ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序
PriorityBlockingQueue:一个具有优先级的无限阻塞队列。
SynchronousQueue:是这样一种阻塞队列,其中每个 插入操作必须等待另一个线程的移除操作,反之亦然。同步队列没有任何内部容量,甚至连一个队列的容量都没有。
DelayedWorkQueue:一个无界阻塞队列。
corePoolSize:当提交任务到线程池时,若当前线程池中线程数量小于corePoolSize,则创建线程执行任务,否则将任务加入阻塞队列。
maximumPoolSize:线程池中允许创建的最大的线程数量,如果任务队列满了而且线程池中的线程数量小于允许创建的最大线程数,那么创建新的线程执行任务。若线程池使用了无界队列,那么该参数不起作用。
RejectedExecutionHandler:当ThreadPoolExecutor已经关闭或ThreadPoolExecutor已经饱和时(达到了最大线程池大小且工作队列已满),execute()方法将要调用的Handler。
Executor框架提供了工具类Executors,可以用来创建4中类型的Executor。FixedThreadPool、SingleThreadPool、CachedThreadPool()和SheduledThreadPool。其中前三种创建ThreadPoolExecutor,最后一种创建ScheduledThreadPoolExecutor。
(1).FixedThreadPool:
注意到api中,corePoolSize=maximumPoolSize=nThreads。当线程池中线程数量大于corePoolSize时,多余空闲线程会立刻中止(因为参数keepAliveTime设置为0)。参数keepAliveTime表示:空闲线程等待新任务的最长时间,若多于此时间没有新任务执行,那么该线程将终止。FixedThreadPool使用了无界队列LinkedBlockingQueue。因此,maximumPoolSize是一个无效参数。当线程池中线程数量为corePoolSize时,当有新的任务提交时会被放在阻塞队列,所以线程池中线程数量永远不会大于corePoolSize。以为使用了无界队列,FixedThreadPool永远不会处于饱和状态,因此,运行的FixedThreadPool将不会执行任务(执行RejectedExecutionHandler.rejectedExecution)。
(2).SingleThreadPool:
注意到该api中,corePoolSize=maximumPoolSize=1。SingleThreadPool是使用单个工作线程的Executor,同样的,SingleThreadPool也使用了无界阻塞队列作为线程池的工作队列。其所带来的影响如上文标注的红色部分。
(3).CachedThreadPool:
一个无界的、可以自动回收的线程池。该api 中,corePoolSize=0,maximumPoolSize=Integer.MAX_VALUE,CachedThreadPool使用了SynchronousQueue作为阻塞队列。在上文介绍中,我们知道SynchronousQueue是没有容量的,但maximumPoolSize被设置为Integer.MAX_VALUE因此线程池可以创建的线程数量是无界的。当有任务提交时,CachedThreadPool中若无空闲线程将会创建新线程来执行任务。若任务提交的速度超过CacedThreadPool处理速度,那么CachedThreadPool将会源源不断创建新线程,极端情况下CachedThreadPool会因为创建过多线程耗尽CPU和内存。当CachedThreadPool处于饱和状态时,创建的线程数大于Integer.MAX_VALUE时,将会调用Handler的RejectedExecutionHandler.rejectedExecution。
(4)SheduledThreadPool:
SheduledThreadPoolExecutor继承ThreadPoolExecutor。其主要作用是用来在给定延迟之后执行任务,或定期执行任务。
创建corePoolSize大小为corePoolSize的线程池,跟踪源码可以发现maximumPoolSize=Integer.MAX_VALUE,其使用的阻塞队列为DelayedWorkQueue。如下图所示:
DelayedWorkQueue是一个无界队列,因此参数maximumPoolSize在此是无效的。
第三部分:在第一部分中,简单的提到了FutureTask。下面简单介绍一下Future和FutureTask。
Future模式:拿我们平时去餐厅吃饭来说,当我们点完餐,可以等待去做自己的事情,等待服务员把饭送过来。Future模式就是当我们提交了一个请求,服务器可能无法给我们响应,若在的单线程环境下,调用函数是同步的,请求会一直等知道返回结果。但利用Future模式,可以去干其他的事情。 在Future模式中,调用函数是异步的,利用这段时间可以干其他的事情。如下图所示(图片摘自网络)
上图中,当客户端发出请求是,左侧需要一直等待调用结束返回结果之后才能直接性other_job;而对于右侧,当对于实现了Future模式的客户端提交了请求后,
服务器会立即给其返回一个伪结果(并不是真实的结果),便可以提交其他的任务other_job,而无须一直等待任务完成。显然Future模式给用户带来了更好的体验,
提高了系统的吞吐量和性能。而类FutureTask则是JDK实现了Future模式的典型代表。Future接口和实现Future接口的FutureTask类,代表异步计算的结果。
对于FutureTask,我们前面说了其实现了Runnable和Future接口。因此可以被作为线程执行,根据FutureTask的run方法执行的时机,可将其分为三个状
态:(1)未启动:创建了FutureTask对象,但run方法未执行之前,(2)已启动:run方法执行的过程中,(3)已完成:run方法执行完正常结束,或者被取消(调用cancle方法),或者在执行的过程中抛出异常而结束。如下图所示(摘自java并发编程的艺术):
当FutureTask处于未启动状态和已启动状态时时,调用get方法将导致调用线程阻塞。而当处于已完成状态时,调用get方法将将返回结果或抛出异常。
当FutureTask处于未启动状态时,若此时调用cancle方法,那么该任务永远不会被执行;若处于已启动状态调用cancle方法,将以中断执行此任务的线程的
方式来试图停止任务;若处于已完成状态调用cancle方法,则对任务没有影响。
可参考如下例子例子:
参考文献:java并发编程的艺术,http://www.infoq.com/cn/articles/executor-framework-thread-pool-task-execution-part-01 如有维权,请联系我。

浙公网安备 33010602011771号