任务执行
Executor框架
任务是一组逻辑工作单元,而线程则是使任务异步执行的机制。Executor框架提供了一种标准的方法将任务的提交过程与执行过程解耦开来,并用Runnable来表示任务。
线程池
可以通过调用Executor中的静态工厂方法之一来创建一个线程池:
- newFixedThreadPool。创建一个固定长度的线程池,每当提交一个任务时就创建一个线程,直到达到线程池的最大数量,这时线程池的规模将不再变化(如果某个线程由于发生了未预期的Exception而结束,那么线程池会补充一个新的线程)。
- newCachedThreadPool。 创建一个可缓存的线程池,如果线程池的当前规模超过了处理需求时,将会回收空闲的线程,而当需求增加时,则可以添加新的线程,线程池的规模不存在任何限制。
- newSingleThreadExecutor。一个单线程的Executor,他创建单个工作者线程来执行任务,如果这个线程异常结束,会创建另一个线程来替代。newSingleThreadExecutor能确保依照任务在队列中的顺序来串行执行。
- newSingleThreadExecutor。创建一个固定长度的线程池,而且以延迟或定时的方式来执行任务,类似Timer。
Executor的生命周期
为解决执行服务的生命周期问题,Executor拓展了ExecutorService接口,添加了一些用于生命周期管理和用于任务提交的方法。ExecutorService的生命周期有3种状态:运行、关闭和已终止。
shutdown方法将执行平缓的关闭过程:不再接受新的任务,同时等待已经提交的任务执行完成–包括那些还未开始执行的任务。
shutdownNow方法将执行粗暴的关闭过程:它将尝试取消所有运行中的任务,并且不再启动队列中尚未开始执行的任务。
等所有任务都完成后,ExecutorService将转入终止状态。可以调用awaitTermination来等待ExecutorService进入终止状态,或者通过调用isTerminated来轮询ExecutorService是否已经终止。通常在调用awaitTermination之后会立即调用shutdown,从而产生同步地关闭ExecutorService的效果。携带结果的任务Callable与Future
Callable能够返回一个值或者抛出一个异常。
Executor执行的任务有4个生命周期阶段:创建、提交、开始和完成。在Executor框架中,已提交但尚未开始的任务可以取消,但对于那些已经开始执行的任务,只有当他们能响应中断时,才能取消。取消一个已完成的任务不会有任何影响。
Future表示一个任务的生命周期,并提供了相应的方法来判断是否已经完成或取消,以及获取任务的结果和取消任务等。CompletionService:Executor与BlockingQueue
如果向Executor提交了一组计算任务,并且希望在计算完成后获取结果,则可以使用CompletionService。其将Executor和BlockingQueue的功能融合在一起。CompletionService将计算完成的任务放入BlockingQueue,因此主线程获取任务返回值的顺序与任务提交的顺序并不一致。
取消与关闭
Java并没有提供任何机制来安全地终止线程。但它提供了中断(Interruption),这是一种协作机制,能够使一个线程终止另一个线程的当前工作。
任务取消
如果外部代码能在某个操作正常完成之前将其置入“完成”状态,那么这个操作就可以成为可取消的。
在Java中没有一种安全地抢占式方法来停止线程,因此也就没有安全的抢占式方法来停止任务,只有一些协作式的机制,使请求取消的任务和代码都遵循一种协商好的协议。其中一种协作机制能设置某个“已请求取消”标志,而任务将定期查看这个标志。例如:
1 | private volatile boolean canceled; |
然而,如果使用这种方法的任务调用了一个阻塞方法,那么任务可能永远不会检查取消标志,因此永远不会结束。
每个线程都有一个boolean类型的中断状态。当中断线程时,这个线程的中断状态将被设置为true。阻塞库方法,例如Thread.sleep和Object.wait等,都会检查线程何时中断,并且在发现中断时提前返回。它们在响应中断时执行的操作包括:清除中断状态,抛出InterruptedException,表示阻塞操作由于中断而提前结束。
通常,中断是实现取消的最合理方式。