chapter06_任务执行_3_找出可利用的并行性

  • 使用Executor就必须要将任务表述为一个Runnable, 就__必须有明确的任务边界__。

    对于服务器应用程序, 自然边界就是一个客户请求, 但其实可以继续细分, 挖掘潜在的并行性

  • Callable和Future

    (1) Runnable的__局限性__在于, 不能返回值或抛出一个受检查异常

    (2) 为了解决Runnable的局限性, Callable是一种更好的抽象: 它返回一个值, 并且可以抛出异常

      public interface Callable<V> {
    
          V call() throws Exception;
      }
    

    (3) Future表示一个任务的生命周期, 并且提供了相应的方法来判断是否已经完成或取消、获取任务的结果和取消任务等。在Future规范中隐含的表示了, 任务的生命周期只能前进不能后退

    (4) Future中的get()方法取决于任务的状态(尚未开始、正在运行、已完成)

    如果任务没有完成, 将会阻塞;

    如果任务已经完成, 将会立即返回结果或者抛出Exception: 如果任务抛出异常, 则get会将异常封装为ExecutionException重新抛出; 如果任务被取消, 会抛出CancellationException

    (5) 创建一个Future任务的方法

    1° ExecutorService中的所有submit()都会返回一个Future

    2° 显示的创建一个FutureTask实例, 因为FutureTask实现了Future和Runnable接口(见"FutureTask.uml"), 所以可以传给Executor或者run

  • 浏览器的页面渲染器示例

    (1) 不好的串行示例

      public abstract 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);
              }
          }
    
          interface ImageData {
          }
    
          interface ImageInfo {
              ImageData downloadImage();
          }
    
          abstract void renderText(CharSequence s);
    
          abstract List<ImageInfo> scanForImageInfo(CharSequence s);
    
          abstract void renderImage(ImageData i);
      }
    

    (2) 使用Future实现

      public abstract 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) {
          
                  // Re-assert the thread's interrupted status
                  Thread.currentThread().interrupt();
                  // We don't need the result, so cancel the task too
                  future.cancel(true);
          
              } catch (ExecutionException e) {
          
                  throw launderThrowable(e.getCause());
              }
          }
    
          interface ImageData {
          }
    
          interface ImageInfo {
              ImageData downloadImage();
          }
    
          abstract void renderText(CharSequence s);
    
          abstract List<ImageInfo> scanForImageInfo(CharSequence s);
    
          abstract void renderImage(ImageData i);
      }
    

    这个示例把下载并得到图片数据作为单独一个任务submit给executor, 然后需要渲染的时候用Future对象get(), 实现了并行性

    但是, 这样做的问题是两个任务的完成时间不一样, 渲染文本的任务(主线程)是个快操作, 下载图片的操作是个慢操作, 相当于快操作早早完成再等待慢操作, 并没有性能上的显著提升

    (3) 使用CompletionService

    为了解决(2)存在的问题, 可以继续将任务分解: 把每张图片的下载过程都分解为一个任务;

    这样相当于给executor提交了很多个任务, 每个任务都关联一个Future对象,使用轮询的方式判断任务有没有完成,这样做可行但是繁琐;

    一种好的解决方式是使用CompletionService, 它关联一个Executor对象, 并且可以take()方法__获得已经完成的结果__,并且这些结果在完成时会被封装成Future返回。

    示例

      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());
              }
          }
    
          interface ImageData {
          }
    
          interface ImageInfo {
              ImageData downloadImage();
          }
    
          abstract void renderText(CharSequence s);
    
          abstract List<ImageInfo> scanForImageInfo(CharSequence s);
    
          abstract void renderImage(ImageData i);
      }
    

    注意CompletionService的构造函数, 事实上多个CompletionService对象可以共享一个Executor对象。 而所有线程池都是Executor对象, 也就是说不会因为创建了多个线程池导致内存上耗尽

  • 为任务设置时限

    (1) 有这样一种需求: 让一个任务在指定时间内做完, 如果超出了指定的时限就使用默认的结果

    满足这样需求的是Future对象的带限时的get()方法

      V get(long timeout, TimeUnit unit)
    

    如果超时则抛出TimeoutException异常

    (2) 注意任务超时后应该立刻停止这个任务, 此时可以使用Future的cancel()方法

    (3) 示例: 加载广告, 如果超时则放上默认的广告

      public class RenderWithTimeBudget {
    
          private static final Ad DEFAULT_AD = new Ad();
          private static final long TIME_BUDGET = 1000;
          private static final ExecutorService exec = Executors.newCachedThreadPool();
    
          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;
          }
    
          private Page renderPageBody() {
              return new Page();
          }
    
          static class Ad {
          }
    
          static class Page {
              public void setAd(Ad ad) {
              }
          }
    
          static class FetchAdTask implements Callable<Ad> {
              public Ad call() {
                  return new Ad();
              }
          }
      }
    

    添加了ad = f.get(timeLeft, NANOSECONDS);用于在指定时间内获得结果, 如果超时则抛出TimeoutException: 捕获这个异常并取消这个任务f.cancel(true);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值