大部分程序都是围绕task执行进行组织。task是work的抽象、具体单元。将work分解为task简化应用的组织。
在线程中执行task...
第一步是识别出明显的边界,理想情况是task是独立活动,不依赖状态、结果和其他task。大部分服务器按照客户请求作为task的边界。
Executor框架:
task是work的逻辑单元,线程可看作一种让task可以异步运行的机制。有界队列限制应用因为过载导致内存耗尽的,线程池在管理线程上
也可以获得同样好处。作为Executor框架的一部分,java.uitl.concurrent包提供一个弹性线程池实现类。在java库中task执行的最原始
抽象是Executor而不是Thread。Executor接口定义如下:
public interface Executor {
public void execute(Runnable command);
}
Executor虽然是一个简单的接口,可它是构建一个弹性而强壮的异步task执行框架的基础,支持不同的task执行策略,提供了标准的task
执行和提交解耦方式。Executor的实现类中还添加了生命周期管理,通过hook的实现统计,应用管理和监控。Executor是基于生产-消费模式,
提交task的角色是生产着,执行task的线程被看作消费者。使用Executor是实现消费-生产模式的最简单路径。执行策略是资源管理工具,可选
的策略依赖可用的计算资源和服务质量。执行策略说明与task提交分离使得策略的选择更加有实践性。当你看到new Thread(task).start()
形式的代码,需要考虑是不是要使用Executor替换。
线程池:
线程池管理同质工作线程池,线程同一个工作队列仅仅绑定在一起,在队列中保存着等待执行的task,worker线程的生命周期不复杂,首先从
队列中请求task,执行task完后等待下一个任务。在线程池中执行task的比在每个task对应一个线程更有优势。java库中提供一个带有预配置的
弹性线程池实现,调用Executors工具类创建并使用它。
newFixedThreadPool: 线程数量固定的线程池,任务提交到线程池时创建一个线程直到达到最大的线程数,尝试保持常量的线程;
newCacheThreadPool: 缓存线程池提供更大的弹性,当请求量小于请求量,回收闲置的线程;当请求量增大时则新增线程。此线程池没有线程数上线;
newSingleThreadExecutor:single-threaded执行器创建唯一的工作线程处理task,当发生异常时,创建新的线程替换它。
newScheduledThreadPool: 固定大小线程池,支持延时和周期task执行,类似于Timer
execute方法提交了一个task,task被添加到工作队列中,工作线程反复出队列并执行task.
executor生命周期:
我们到目前为止只看到如何创建一个Executor,但不清楚如何关闭它。jvm的退出是在所有的线程终止之后,因此如果执行executor关闭操作失败将导致
jvm无法退出。因为executor执行task采用异步方式,所以在任何时刻都无法马上清楚已提交的task的状态。Executor提供关闭服务。为了解决生命周期
问题,ExecutorService继承Executor,添加一些生命周期管理的功能。
public interface ExecutorService extends Executor {
void shutdown();
List<Runnable> shutdownNow();
boolean isShutdown();
boolean isTerminated();
boolean awaitTermination(long timeout, TimeUnit unit) throws InterrupttedException;
// additional convenience methods fors task submission
}
ExecutorService中涉及的生命周期总共是三个,运行、关闭和终止。ExecutorService在初始化完后处于运行状态,shutdown方法触发优雅的关闭工作,
当没有新task被传送进来且已传递的task允许执行完成,包括还没执行的task。shutdownNow方法触发猝然关闭,它尝试取消正执行的任务并放弃
未执行task的执行。当executorService处于关闭状态,提交的task将被拒绝。
延时和周期任务
Timer设施管理延时和周期任务,然而Timer存在一些缺陷,ScheduledThreadPoolExecutor可视为它的替代。可通过构造函数或者newScheduledThreadPool
工厂方法创建一个ScheduledThreadPoolExecutor实例。Timer按照绝对时间调度,不支持相对时间,ScheduledThreadPoolExecutor却支持相对时间。
寻找可利用的并发
Executor框架使用Runnable作为它基本的task表现,Runnable有其局限性,例如无法返回结果或者抛出异常。很多task具有明显的延时计算,例如数据库
查询,通过网络中抓取某中资源。对于这类task, Callable是更符合的抽象,它主要的入口call函数会返回结果并且会抛出异常,Executor提供许多包装
方法将Runnable实例封装成Callable实例。task生命周期也是有限的,它们有一个清晰的起点和终点。task的生命周期的四个阶段:创建、提交、开始和结束。
因为task可能需要执行很长时间,此时我们希望能够取消task的执行。在Executor框架中,任务被提交但没有开始的情况下可以被取消。当任务已经开始
但是返回中断的情况下任务可能已被取消。取消一个执行完成的task没有任何负面影响。Future代表任务的生命周期且提供方法检测任务已经完成或者
被取消、检索结果或取消task.
public interface Callable<V> {
V call() throws Exception;
}
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException, CancellationException;
V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, CancellationException, TimeoutException;
}
有很多手段创建描述task的Future,ExecutorService中submit函数返回一个Future,直接创建FutureTask实例。在java6的实现里,ExecutorService的实现
AbstractExecutorService的newTaskFor函数可被覆盖,实现Future对提交的Callable和Runnable的实例化。在ThreadPoolExecutor类newTaskFor默认的实现
代码如下:
public <T> RunnableFuture<T> newTaskFor(Callable<T> task) {
return new FutureTask<T>(task);
}
案例:通过Future实现页面渲染
首先将需求分解成两个任务,一个任务负责页面的渲染,另一个任务负责下载所有的照片。
实现:创建一个下载照片Callable任务,将其提交给executorService,ExecutorService返回描述Task的Future实例,当主线程执行到需要照片数据时,它
等待从Future.get返回。