一步步优化页面渲染功能 java
本节将模拟一个简单的页面渲染功能,它的做用是将HTML页面绘制到图像缓存中,为了简便,假设HTML文件只包含标签文本以及预订大小的图片和URL。编程
一、串行的页面渲染器缓存
最简单的实现方式是对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利用率和响应灵敏度。并发
二、使用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()); } } }
固然,咱们还能够优化,用户其实不须要等待全部图像下载完成,咱们能够每下载完一张图像就马上显示出来。优化
三、使用CompletionService实现页面渲染器网站
要实现下载完一张就马上绘制,咱们须要及时知道图片下载完成,对于这种场景,CompletionService十分符合需求。CompletionService将生产新的异步任务与使用已完成任务的结果分离开来的服务。生产者 submit 执行的任务,使用者 take 已完成的任务,并按照完成这些任务的顺序处理它们的结果。下面的代码使用CompletionService改写了页面渲染器的实现:this
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并发编程实战》