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 作到這一點。圖片