我们在开发中可能会遇到这种情况:
有时候如果某个任务无法在指定时间内完成,那么将不再需要它的结果,此时可以放弃这个任务。
例如:某个Web应用程序从外部的广告服务器上获取广告信息,但如果该应用程序在2s中不能得到响应,那么将显示一个默认的广告,类似的,一个门户网站可以从多个数据源并行的获得数据,但可能只会在指定的时间内等待数据,如果超出了等待时间,那么只显示已经得到的数据。
1.在有限的时间内执行任务的主要困难在于:要确保得到答案的时间不会超过限定的时间,或者在限定的时间内无法获得答案。
我们可以通过Futrue中的get方法解决这个问题:
上面的方法的执行逻辑是,当结果可用时,它将立即返回,如果在指定时限内没有计算出结果,那么将抛出TimeoutException.
2.在使用限时任务时要注意,当这些任务超时后应当立即停止,从而避免为计算一个不需要的结果而浪费资源。 如果一个限时的get方法抛出一个TimeoutException异常,那么我们可以通过Future来取消任务,如果编写的任务是可取消的,那么我们可以提前终止它。
例子:
在指定时间获取广告信息
Page renderPageWithAd() throws InterruptedException{
long endNanos = System.nanoTime() + TIME_BUDGET;//指定等待的时间
Future<Ad> f = exec.submit(new FetchAdTask());//提交获取广告任务
//在等待广告的同时显示页面
Page page = renderPageBody();
Ad ad;
try{
long timeLeft = endNanos - System.nanoTime();//只等待指定时间
ad = f.get(timeLeft,NANOSECONDS);//获取广告结果
}catch(ExecutionException e){
ad=DEFAULT_AD;//如果获取失败,返回默认广告
}catch(TimeoutException e){//如果超时,返回默认广告,并取消这个任务
ad = DEFAULT_AD;
f.cancel(true);
}
page.setAd(ad);//给页面设置广告
return page;//返回页面
}
3.我们可以创建n个任务,将其提交到一个线程池,保留n个Future,并使用限时的get方法通过Future串行地获取每一个结果,这一切都很简单,但还有一个更简单的方法-----invokeAll
InvokeAll方法的参数为一组任务,并返回一组Future,invokeAll按照任务集合中迭代器的顺序将所有的Future添加到返回的集合中,从而使调用者能将各个Future与其表示的Callable关联起来,当所有任务都执行完毕时,或者调用的线程被中断时,又或者超过指定时限时,invokeAll将返回。
当超过时限后,任何还未完成的任务都会取消,当invokeAll返回后,每个任务要么正常地完成,要么被取消,而客户端可以调用get或者isCancelled来判断究竟是何种情况。
实例:在预定时间内请求旅游报价
private class QuoteTask implements Callable<TravelQuote>{
private final TravelCompany company;
private final TravelInfo travelInfo;
QuoteTask (TravelCompany company,TravelInfo traveInfo){
this.company = company;
this.traveInfo = traveInfo;
}
public TravelQuote call() throws Exception{
return company.solicitQuote(travelInfo);//请求报价
}
}
public List<TravelQuote> getRankedTravelQuotes(
TravelInfo travelInfo,Set<TravelCompany> companies,
Comparator<TravelQuote> ranking, long time,TimeUnit unit)
throws InterruptedException{
//创建任务列表
List<QuoteTask> tasks = new ArrayList<QuoteTask>();
//根据公司数来向任务列表中添加参数
for(TranvelCompany company: companies)
tasks.add(new QuoteTask(company,travelInfo));
//提交一组任务
List<Future<TravelQuote>> futures = exec.invokeAll(tasks,time,unit);
//创建报价列表
List<TravelQuote> quotes = new ArrayList<TravelQuote>(tasks.size());
//创建任务列表的迭代器
Iterator<QuoteTask> taskIter = tasks.iterator();
//遍历任务的执行结果并将其添加到报价列表中
for(Future<TravelQuote> f: futures){
QuoteTask task = taskIter.next();
try{
quotes.add(f.get());
}catch(ExecutionException e){
quotes.add(task.getFailureQuote(e.getCause()));
}catch(CancellationException e){
quotes.add(task.getTimeoutQuote(e));
}
}
}
总结:通过围绕任务执行来设计应用程序,可以简化开发过程,有助于实现并发。Executor框架将任务提交和执行策略解耦开来,同时还支持多种不同类型的执行策略,当需要创建线程来执行任务时,我们可以考虑使用Executor。
要想在将应用程序分解为不同的任务时获得最大的好处,必须定义清晰的任务边界,某些应用程序中存在这比较明显的任务边界,而在其他一些程序中则需要进一步分析才能揭示出粒度更细的并行性。