一步步优化页面渲染功能                                                          

本节将模拟一个简单的页面渲染功能,它的作用是将HTML页面绘制到图像缓存中,为了简便,假设HTML文件只包含标签文本以及预订大小的图片和URL。

1、串行的页面渲染器

最简单的实现方式是对HTML文档进行串行处理:先绘制文本,然后绘制图像,串行处理:

public class SingleThreadRenderer {
void renderPage(CharSequence source) {
renderText(source);
List<ImageData> imageData = new ArrayList<ImageData>();
for (ImageInfo imageInfo : scanForImageInfo(source))
imageData.add(imageInfo.downloadImage());
for (ImageData data : imageData)
renderImage(data);
}
}

  这种实现方式有个问题,因为图像下载过程的大部分时间都是在等待I/O操作执行完成,在这期间CPU几乎不做任何工作。因此,这种执行方式没有充分地利用CPU,使得用户在看到最终页面之前要等待过长时间。通过将问题分解为多个独立的任务并发执行,能够活得更高的CPU利用率和响应灵敏度。

2、使用Future实现页面渲染器

为了使页面渲染器实现更高的并发性,首先将渲染过程分解为两个任务,一个是渲染所有的文本,另一个是下载所有的图像(一个是CPU密集型,一个是I/O密集型)。Callable和Future有助于表示这种协同任务的交互,以下代码首先创建一个Callable来下载所有的图像,当主任务需要图像时,它会等待Future.get的调用结果。如果幸运的话,图像可能已经下载完成,即使没有,至少也已经提前开始下载。

public class FutureRenderer {
private final ExecutorService executor = Executors.newCachedThreadPool(); void renderPage(CharSequence source) {
final List<ImageInfo> imageInfos = scanForImageInfo(source);
Callable<List<ImageData>> task =
new Callable<List<ImageData>>() {
public List<ImageData> call() {
List<ImageData> result = new ArrayList<ImageData>();
for (ImageInfo imageInfo : imageInfos)
result.add(imageInfo.downloadImage());
return result;
}
}; Future<List<ImageData>> future = executor.submit(task);
renderText(source); try {
List<ImageData> imageData = future.get();
for (ImageData data : imageData)
renderImage(data);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
future.cancel(true);
} catch (ExecutionException e) {
throw launderThrowable(e.getCause());
}
}
}

  当然,我们还可以优化,用户其实不需要等待所有图像下载完成,我们可以每下载完一张图像就立刻显示出来。

3、使用CompletionService实现页面渲染器

要实现下载完一张就立刻绘制,我们需要及时知道图片下载完成,对于这种场景,CompletionService十分符合需求。CompletionService将生产新的异步任务与使用已完成任务的结果分离开来的服务。生产者 submit 执行的任务,使用者 take 已完成的任务,并按照完成这些任务的顺序处理它们的结果。下面的代码使用CompletionService改写了页面渲染器的实现:

public abstract class Renderer {
private final ExecutorService executor; Renderer(ExecutorService executor) {
this.executor = executor;
} void renderPage(CharSequence source) {
final List<ImageInfo> info = scanForImageInfo(source);
CompletionService<ImageData> completionService =
new ExecutorCompletionService<ImageData>(executor);
for (final ImageInfo imageInfo : info)
completionService.submit(new Callable<ImageData>() {
public ImageData call() {
return imageInfo.downloadImage();
}
}); renderText(source); try {
for (int t = 0, n = info.size(); t < n; t++) {
Future<ImageData> f = completionService.take();
ImageData imageData = f.get();
renderImage(imageData);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (ExecutionException e) {
throw launderThrowable(e.getCause());
}
}
}

为任务设置时限                                                                       

有时候,如果某个任务无法在指定时间内完成,那么将不再需要它的结果,此时可以放弃这个任务。例如,某个Web应用程序从外部的广告服务器上获取广告信息,但是如果该应用程序在两秒内得不到响应,那么将显示一个默认的广告页,这样即使不能活得广告信息,也不会降低站点的响应性能,对于这种需求,Future.get方法可以实现:

    Page renderPageWithAd() throws InterruptedException {
long endNanos = System.nanoTime() + TIME_BUDGET;
Future<Ad> f = exec.submit(new FetchAdTask());
// Render the page while waiting for the ad
Page page = renderPageBody();
Ad ad;
try {
// Only wait for the remaining time budget
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;
}

  这种"预订时间"的方法可以很容易地扩展到任意数量的任务上,考虑这样一个旅行网站:用户输入旅行日期及要求,网站通过多种途径获取结果,此时,不应该让页面的响应时间受限于最慢的途径,而应该只显示在指定时间内收到的消息,我们可以通过使用支持限时的invokeAll,将多个任务提交到一个ExecutorService的方式实现这个需求:

public class TimeBudget {
private static ExecutorService exec = Executors.newCachedThreadPool(); 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 (TravelCompany 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));
}
} Collections.sort(quotes, ranking);
return quotes;
} } class QuoteTask implements Callable<TravelQuote> {
private final TravelCompany company;
private final TravelInfo travelInfo; public QuoteTask(TravelCompany company, TravelInfo travelInfo) {
this.company = company;
this.travelInfo = travelInfo;
} TravelQuote getFailureQuote(Throwable t) {
return null;
} TravelQuote getTimeoutQuote(CancellationException e) {
return null;
} public TravelQuote call() throws Exception {
return company.solicitQuote(travelInfo);
}
} interface TravelCompany {
TravelQuote solicitQuote(TravelInfo travelInfo) throws Exception;
} interface TravelQuote {
} interface TravelInfo {
}

例子来自:《Java并发编程实战》

  

Java并发(具体实例)——几个例子的更多相关文章

  1. Java并发(三):实例引出并发应用场景

    前两篇介绍了一些Java并发的基础知识,博主正巧遇到一种需求:查询数据库,根据查询结果集修改数据库记录,但整个流程是做成了一个schedule的,并且查询比较耗时,并且需要每两分钟执行一次,cpu经常 ...

  2. Java并发编程实例(synchronized)

    此处用一个小程序来说明一下,逻辑是一个计数器(int i):主要的逻辑功能是,如果同步监视了资源i,则不输出i的值,但如果没有添加关键字synchronized,因为是两个线程并发执行,所以会输出i的 ...

  3. Java并发工具类CountDownLatch源码中的例子

    Java并发工具类CountDownLatch源码中的例子 实例一 原文描述 /** * <p><b>Sample usage:</b> Here is a pai ...

  4. Java并发编程 | 从进程、线程到并发问题实例解决

    计划写几篇文章讲述下Java并发编程,帮助一些初学者成体系的理解并发编程并实际使用,而不只是碎片化的了解一些Synchronized.ReentrantLock等技术点.在讲述的过程中,也想融入一些相 ...

  5. Java并发(具体实例)—— 构建高效且可伸缩的结果缓存

    这个例子来自<Java并发编程实战>第五章.本文将开发一个高效且可伸缩的缓存,文章首先从最简单的HashMap开始构建,然后分析它的并发缺陷,并一步一步修复. hashMap版本     ...

  6. JAVA并发编程J.U.C学习总结

    前言 学习了一段时间J.U.C,打算做个小结,个人感觉总结还是非常重要,要不然总感觉知识点零零散散的. 有错误也欢迎指正,大家共同进步: 另外,转载请注明链接,写篇文章不容易啊,http://www. ...

  7. 【Java并发系列04】线程锁synchronized和Lock和volatile和Condition

    img { border: solid 1px } 一.前言 多线程怎么防止竞争资源,即防止对同一资源进行并发操作,那就是使用加锁机制.这是Java并发编程中必须要理解的一个知识点.其实使用起来还是比 ...

  8. java并发编程(2)--volatile(转)

    转载:http://ifeve.com/volatile/ 作者:方 腾飞 花名清英,并发网(ifeve.com)创始人,畅销书<Java并发编程的艺术>作者,蚂蚁金服技术专家.目前工作于 ...

  9. Java并发编程:如何创建线程?

    Java并发编程:如何创建线程? 在前面一篇文章中已经讲述了在进程和线程的由来,今天就来讲一下在Java中如何创建线程,让线程去执行一个子任务.下面先讲述一下Java中的应用程序和进程相关的概念知识, ...

随机推荐

  1. springboot 基于@Scheduled注解 实现定时任务

    前言 使用SpringBoot创建定时任务非常简单,目前主要有以下三种创建方式: 一.基于注解(@Scheduled) 二.基于接口(SchedulingConfigurer) 前者相信大家都很熟悉, ...

  2. #20175201 实现mypwd

    实验要求 学习pwd命令 研究pwd实现需要的系统调用(man -k; grep),写出伪代码 实现mypwd 测试mypwd 了解pwd命令   pwd是"Print Working Di ...

  3. Cordova-在现有iOS工程自动化接入Cordova插件

    模拟Cordova插件命令 自己编写脚本,了解cordova添加插件做了哪些事情. 上一篇文章了解到,web与native的交互主要是cordova.js中的exec方法调用,触发交互事件.UIWeb ...

  4. maven之pom.xml的配置

    pom.xml是配置文件: <dependencies>表示依赖,里面可以有多个<dependency> 比如当前使用了junit的jar包,版本是3,8,1,我们现在更换新的 ...

  5. 安装mysql出现Couldn't find MySQL server (/usr/bin/mysqld_safe)

    安装mysql出现Couldn't find MySQL server (/usr/bin/mysqld_safe)Starting MySQL ERROR! Couldn't find MySQL ...

  6. Jmeter之循环控制器

    在使用Jmeter测试时,部分接口需要循环执行多次,这时候就可以使用循环控制器去控制执行. 循环控制器如下图: 说明 : (1.名称:标识,建议明确此循环控制器的使用的作用是什么(如:登录循环控制) ...

  7. Apache hadoop namenode ha和yarn ha ---HDFS高可用性

    HDFS高可用性Hadoop HDFS 的两大问题:NameNode单点:虽然有StandbyNameNode,但是冷备方案,达不到高可用--阶段性的合并edits和fsimage,以缩短集群启动的时 ...

  8. 【ABAP系列】SAP ABAP DYNP_VALUES_UPDATE 更新屏幕字段的函数及用法

    公众号:SAP Technical 本文作者:matinal 原文出处:http://www.cnblogs.com/SAPmatinal/ 原文链接:[ABAP系列]SAP ABAP DYNP_VA ...

  9. [Web 前端] 028 jQuery 事件

    目录 jQuery 的事件 1. 事件绑定 1.1 事件的获取 1.2 基本绑定 1.3 动态绑定 2. 事件触发 2.1 触发的写法 2.2 常用的鼠标事件 3. 事件冒泡和默认行为 3.1 事件冒 ...

  10. maven中央仓库太慢的解决办法

    在.m2目录下创建settings.xml文件,文件内容如下: <?xml version="1.0" encoding="UTF-8"?> < ...