Head First Java Future

介绍:

Future是JDK7中并发包里新增的一个接口, 持有的范型 即为调用Future的get()返回的值类型.

Future对象被视作为一个异步计算任务的结果对象, 它提供方法去检测异步任务是否已完成, get()过程中当前线程会进入阻塞状态等待result的返回. 它的get()方法可以保证在异步任务未结束之前不会返回任何值, 从而保证了异步任务执行的完整性, 当然也可以为get()设置一个timeout, 超时则自动停止阻塞状态; 并且提供了cancel()方法来取消当前任务.

一个比较常见的场景: 通过ExecutorService执行了一个异步任务并返回一个future对象, 当前线程接着去做一些其他的事务, 而此任务并没有设置相应的任务回调, 所以即使完成了任务也没办法通知到当前线程”任务结束”. 所以在当前线程内需要用到任务结果的时候, 可以通过future.get()来获取结果, 当然在get()的时候任务可能仍在执行导致当前线程进入阻塞状态等待任务结束或者抛出异常, 所以可能会产生InterruptedException/ExecutionException 异常。

JDK自带的实现:FutureTask

Guava的相关实现:ListenableFuture

FutureTask 就是一个实现了RunnableFuture接口的类, 被线程池调度出的workThread执行run()函数, 而实际的耗时任务被放进Callable接口里的call(). workThread 在进入到run()内部的时候会通过callable的call()去真正调起耗时任务, 并且该workThread会一直阻塞到耗时计算完成为止(or exception). 所以说配合线程池来实现并发操作真的是太perfect了.

ListenableFuture 在Guava的的此接口简介中, 强烈建议使用ListenableFuture来替代JDK的Future接口, 那么此接口的意义是什么呢?相比较JDK的Future接口, 拓展了什么呢?

传统的Future接口在计算工作完成后, 调用方是不能及时的知道此计算工作的完成时刻, 也就无法确保在一个正确的时间点去拿到执行结果的, 唯一的方式是通过get()去读取这个result, 在get()调用的时候任务仍可能处于执行状态, 那么调用方就可能进入阻塞状态直到计算任务的结束.

对于这样的问题, ListenableFuture提供了一个注册listener机制, 在任务结束结束的时刻即可通过此listener来回馈计算结果. 相比较于Future, ListenableFuture新增的函数为:

addListener(Runnable listener, Executor executor);

其中的参数listener对象就是作为一个回调传入到task的内部, 在workThread开始执行了run()方法并且拿到结果后, 就立马通过listener.run()函数来通知调用方结果可用, 不再需要调用方通过get()去拿一个”未知状态”的result。这样使得并发操作更为简便以及准确了.

顺带着说一下线程池(Executor)与ListenableFuture的完美配合, 前者专注于多线程的调度, 为后者提供一个可靠的运行环境而不需要具体考虑任务执行的逻辑; 后者则专注于任务的执行以及结果的回调而不需要考虑任务执行所在的环境问题. 从而实现了异步编程这个抽象目标.

但是话说回来, 对于嵌套的FutureTask可能就会出现一些问题了, 具体参考gist上的这个 Example