9.5 Future

构建复杂并行操作的另外一种方案是使用FutureFuture像一张欠条,方法不是返回一个值,而是返回一个Future对象,该对象第一次创建时没有值,但以后能拿它“换回”一个值。

调用Future对象的get方法获取值,它会阻塞当前线程,直到返回值。可惜,和回调一样,组合Future对象时也有问题,我们会快速浏览这些可能碰到的问题。

我们要考虑的场景是从外部网站查找某专辑的信息。我们需要找出专辑上的曲目列表和艺术家,还要保证有足够的权限访问登录等各项服务,或者至少确保已经登录。

例9-9使用Future API解决了该问题。在➊处登录提供曲目和艺术家信息的服务,这时会返回一个Future对象,该对象包含登录信息。Future接口支持泛型,可将Future看作是Credentials对象的一张欠条。

例9-9 使用Future从外部网站下载专辑信息

  1. @Override
  2. public Album lookupByName(String albumName) {
  3. Future<Credentials> trackLogin = loginTo("track");
  4. Future<Credentials> artistLogin = loginTo("artist");
  5. try {
  6. Future<List<Track>> tracks = lookupTracks(albumName, trackLogin.get());
  7. Future<List<Artist>> artists = lookupArtists(albumName, artistLogin.get());
  8. return new Album(albumName, tracks.get(), artists.get());
  9. } catch (InterruptedException | ExecutionException e) {
  10. throw new AlbumLookupException(e.getCause());
  11. }
  12. }

在➋处使用登录后的凭证查询曲目和艺术家信息,通过调用Future对象的get方法获取凭证信息。在➌处构建待返回的专辑对象,这里同样调用get方法以阻塞Future对象。如果有异常,我们在➍处将其转化为一个待解问题域内的异常,然后将其抛出。

读者将会看到,如果要将Future对象的结果传给其他任务,会阻塞当前线程的执行。这会成为一个性能问题,任务不是平行执行了,而是(意外地)串行执行。

以例9-9来说,这意味着在登录两个服务之前,我们无法启动任何查找任务。没必要这样:lookupTracks只需要自己的登录凭证,lookupArtists也是一样。我们将理想的行为用图9-3描述出来。

9.5 Future - 图1

图9-3:查询操作不必等待所有登录操作完成后才能执行

可以将对get的调用放到lookupTrackslookupArtists方法的中间,这能解决问题,但是代码丑陋,而且无法在多次调用之间重用登录凭证。

我们真正需要的是不必调用get方法阻塞当前线程,就能操作Future对象返回的结果。我们需要将Future和回调结合起来使用。