文档章节

Java 并发之 Future 接口

编走编想
 编走编想
发布于 2013/11/13 18:11
字数 1054
阅读 1986
收藏 70

简介

Future 是 Java 5 JUC 包中的一个接口,主要提供了三类功能:

任务结果的获取

这个功能由 get 方法提供,它有两种形式的重载。get 方法本身使用起来很简单,需要注意的是它所抛出的异常:

  • ExecutionException 对 Callable 或 Runnable 所抛出的异常的封装,可以通过 Throwable.getCause() 方法获得具体异常。
  • CancellationException 在调用 get 时任务被通过 Future.cancel() 方法被取消所抛出的异常。这个是 运行时异常,但如果你有调用 Future.cancel() 的地方,那还是需要处理的。
  • TimeoutException V get(long timeout, TimeUnit unit) 重载形式所抛出的超时异常。

任务取消

通过代码看 Future 的使用

我们先看一段代码,这个代码是《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 那里获得下载结果呢?依次调用 Future.get() 是个解决办法,但是那样效率并不高,因为第一个有可能是下载速度最慢的,这样会拖累整个页面的渲染,因为我们希望下载完一个图片就渲染一个。

为了解决这个问题,我们可以这样写

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 已经替我们考虑了这个问题。但是这个话题超出了本期范围,我会在接下来的文章里讲到如何更好地解决这个问题。

Future 的使用范围

从上面的例子我们看到,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 做到这一点。

© 著作权归作者所有

共有 人打赏支持
编走编想
粉丝 150
博文 128
码字总数 111395
作品 0
海淀
程序员
私信 提问
加载中

评论(3)

编走编想
编走编想

引用来自“仪山湖”的评论

对于Future的缺陷一个比较常见的做法是继承FutureTask,重写done()函数

对,ExecutorCompletionService 就是这么做的。在 done() 方法里将 task 添加到 completion queue 中。Guava 的 ListeningExecutorService 想必也是如此的原理(推断,没看过其源代码)
仪山湖
仪山湖
对于Future的缺陷一个比较常见的做法是继承FutureTask,重写done()函数
zhangjg
zhangjg
期待下文
读书笔记之《Java并发编程的艺术》-线程池和Executor的子孙们

读书笔记部分内容来源书出版书,版权归本书作者,如有错误,请指正。 欢迎star、fork,读书笔记系列会同步更新 git https://github.com/xuminwlt/j360-jdk module j360-jdk-thread/me.j360....

Hi徐敏
2015/11/11
0
1
Active Object 并发模式在 Java 中的应用

本文主要从以下两个方面进行阐述: 使用 C++ 语言,来描述 Active Object 设计模式。 Java 类库对于这样一个典型的模式做了很好的类库层面的封装,因此对于 Java 的开发者来说,很多关于该设计...

红薯
2010/08/08
308
0
JAVA多线程和并发基础面试问答

多线程和并发问题是Java技术面试中面试官比较喜欢问的问题之一。在这里,从面试的角度列出了大部分重要的问题,但是你仍然应该牢固的掌握Java多线程基础知识来对应日后碰到的问题。(校对注:...

LCZ777
2014/05/26
0
0
JAVA多线程和并发基础面试问答

Java多线程面试问题 1. 进程和线程之间有什么不同? 一个进程是一个独立(self contained)的运行环境,它可以被看作一个程序或者一个应用。而线程是在进程中执行的一个任务。Java运行环境是一...

hanzhankang
2014/01/20
0
0
Java多线程(全)学习笔记(下)

七.Callable和Future接口 C#可以把任意方法包装成线程执行体,包括那些有返回值的方法。Java也从jdk1.5开始,加入了Callable接口用来扩展Runnable接口的功能,Callable接口提供一个call()...

明舞
2014/07/27
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Spring学习记录

Java类定义配置 @Configuration //标记为配置类@ComponentScan //标记为扫描当前包及子包所有标记为@Component的类@ComponentScan(basePackageClasses = {接口.class,...}) //标记为扫描当...

CHONGCHEN
14分钟前
0
0
如何开发一款以太坊(安卓)钱包系列2 - 导入账号及账号管理

这是如何开发一款以太坊(安卓)钱包系列第2篇,如何导入账号。有时用户可能已经有一个账号,这篇文章接来介绍下,如何实现导入用户已经存在的账号。 导入账号预备知识 从用户需求上来讲,导...

Tiny熊
今天
3
0
intellJ IDEA搭建java+selenium自动化环境(maven,selenium,testng)

1.安装jdk1.8; 2.安装intellJ; 3.安装maven; 3.1 如果是单前用户,配置用户环境变量即可,如果是多用户,则需配置系统环境变量,变量名为MAVEN_HOME,赋值D:\Application\maven,往path中...

不最醉不龟归
今天
4
0
聊聊ShenandoahGC的Brooks Pointers

序 本文主要研究一下ShenandoahGC的Brooks Pointers Shenandoah Shenandoah面向low-pause-time的垃圾收集器,它的GC cycle主要有 Snapshot-at-the-beginning concurrent mark包括Init Mark(P......

go4it
昨天
4
0
Makefile通用编写规则

#简单实用的Makefile模板: objs := a.o b.o test:$(objs) gcc -o test $^ # .a.o.d .b.o.d dep_files := $(foreach f,$(objs),.$(f).d) dep_files := $(wildcard $(dep_files)) ifneq ($(d......

shzwork
昨天
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部