Future 是 Java 5 JUC 包中的一个接口,主要提供了三类功能:java
这个功能由 get 方法提供,它有两种形式的重载。get 方法自己使用起来很简单,须要注意的是它所抛出的异常:多线程
Throwable.getCause()
方法得到具体异常。Future.cancel()
方法被取消所抛出的异常。这个是 运行时异常,但若是你有调用 Future.cancel()
的地方,那仍是须要处理的。V get(long timeout, TimeUnit unit)
重载形式所抛出的超时异常。咱们先看一段代码,这个代码是《Java Concurrency in Practise》的 “Listing 6.13. Waiting for Image Download with Future.”。并发
<!-- lang: java --> public class FutureRenderer { private final ExecutorService executor = ...; 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()); } } }
这段代码模拟了一个 HTML 网页渲染的过程。整个渲染过程分红 HTML 文本的渲染和图片的下载及渲染。这段代码为了提升渲染效率,先提交图片的下载任务,而后在渲染文本,文本渲染完毕以后再去渲染图片。因为图片下载是 IO 密集操做,HTML 文本渲染是 CPU 密集操做,因此让二者并发运行能够提升效率。异步
看到这里,确定会有人说,为何只用一个线程去下载全部的图片。若是用多线程去下载图片,效率岂不是更高。的确是这样,可是在提交图片下载以后,如何去从多个 Future 那里得到下载结果呢?依次调用 Future.get() 是个解决办法,可是那样效率并不高,由于第一个有多是下载速度最慢的,这样会拖累整个页面的渲染,由于咱们但愿下载完一个图片就渲染一个。ide
为了解决这个问题,咱们能够这样写线程
public void renderPage(CharSequence source) { List<ImageInfo> imageInfos = scanForImageInfo(source); Queue<Future<ImageData>> imageDownloadFutures = new LinkedList<Future<ImageData>>(); for (final ImageInfo imageInfo : imageInfos) { Future<ImageData> future = executorService.submit(new Callable<ImageData>() { @Override public ImageData call() throws Exception { return imageInfo.downloadImage(); } }); imageDownloadFutures.add(future); } renderText(source); Future<ImageData> future; while ((future = imageDownloadFutures.poll()) != null) { if (future.isDone()) { if (!future.isCancelled()) { try { renderImage(future.get()); } catch (InterruptedException e) { Thread.currentThread().interrupt(); // We don't need the result, so cancel the task too future.cancel(true); } catch (ExecutionException e) { System.out.println(e.getMessage()); renderImage(ImageData.emptyImage()); } } } else { imageDownloadFutures.add(future); } try { Thread.sleep(50); } catch (InterruptedException e) { System.out.println("Interrupt images download."); } } executorService.shutdownNow(); System.out.println("Finish the page render."); }
这段代码是否是很长,其实咱们不用这么辛苦,JDK 已经替咱们考虑了这个问题。可是这个话题超出了本期范围,我会在接下来的文章里讲到如何更好地解决这个问题。code
从上面的例子咱们看到,Future 是有其局限性的。Future 主要功能在于获取任务执行结果和对异步任务的控制。但若是要获取批量任务的执行结果,从上面的例子咱们已经能够看到,单使用 Future 是很不方便的。其缘由在于:一是咱们没有好的方法去获取第一个完成的任务;二是 Future.get 是阻塞方法,使用不当会形成线程的浪费。解决第一个问题能够用 CompletionService 解决,CompletionService 提供了一个 take() 阻塞方法,用以依次获取全部已完成的任务。对于第二个问题,能够用 Google Guava 库所提供的 ListeningExecutorService 和 ListenableFuture 来解决。这些都会在后面的介绍。接口
除了获取批量任务执行结果时不便,Future 另一个不能作的事即是防止任务的重复提交。要作到这件事就须要 Future 最多见的一个实现类 FutureTask 了。《Java Concurrency in Practice》中的例子“Listing 5.19. Final Implementation of Memoizer”便展现了如何使用 FutureTask 作到这一点。图片