疯狂java


您现在的位置: 疯狂软件 >> 新闻资讯 >> 正文

Java并发具体实例


 

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

  1、串行的页面渲染器

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

  public class SingleThreadRenderer {

  void renderPage(CharSequence source) {

  renderText(source);

  List imageData = new ArrayList();

  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 imageInfos = scanForImageInfo(source);

  Callable> task =

  new Callable>() {

  public List call() {

  List result = new ArrayList();

  for (ImageInfo imageInfo : imageInfos)

  result.add(imageInfo.downloadImage());

  return result;

  }

  };

  Future> future = executor.submit(task);

  renderText(source);

  try {

  List 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 info = scanForImageInfo(source);

  CompletionService completionService =

  new ExecutorCompletionService(executor);

  for (final ImageInfo imageInfo : info)

  completionService.submit(new Callable() {

  public ImageData call() {

  return imageInfo.downloadImage();

  }

  });

  renderText(source);

  try {

  for (int t = 0, n = info.size(); t < n; t++) {

  Future 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 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 getRankedTravelQuotes(TravelInfo travelInfo, Set companies,

  Comparator ranking, long time, TimeUnit unit)

  throws InterruptedException {

  List tasks = new ArrayList();

  for (TravelCompany company : companies)

  tasks.add(new QuoteTask(company, travelInfo));

  List> futures = exec.invokeAll(tasks, time, unit);

  List quotes =

  new ArrayList(tasks.size());

  Iterator taskIter = tasks.iterator();

  for (Future 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 {

  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 {

  }