一、Future详解
Future代表着一个异步任务在未来的执行结果,这个结果可以在最终的某个时间节点通过Future的get方法来获得。
对于长时间运行的任务来说,使其异步执行并立即返回一个Future接口是一种比较不错的选择,因为这样可以允许程序在等待结果的同时继续去执行其他的任务,比如如下这些任务。
- 密集型计算(数学和科学计算)。
- 针对大数据的处理计算。
- 通过远程方法调用数据。
二、Future的使用
Future常与Callable联合使用,Future可以获得Callable执行后的返回值。如果想新建一个线程执行一个这个Callable中的call方法而且获得返回值的话我们可以使用以下的思路。
方案一:new Thread(new FutureTask(一个实现了Callable的类的对象)).start();使用FutureTask来接收任务的返回值。
方案二:new一个线程池然后然后提交Callable的实现的对象。使用Future来获得Callable的返回值。具体实现如下: ```java /**- 认识future */ package yxxy.c_026;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.FutureTask; import java.util.concurrent.TimeUnit;
public class T06_Future { public static void main(String[] args) throws InterruptedException, ExecutionException {
FutureTask<Integer> task = new FutureTask<>(()->{
TimeUnit.MILLISECONDS.sleep(500);
return 1000;
}); //new Callable () { Integer call();}
new Thread(task).start();
System.out.println(task.get()); //阻塞
//*******************************
ExecutorService service = Executors.newFixedThreadPool(5);
Future<Integer> f = service.submit(()->{
TimeUnit.MILLISECONDS.sleep(500);
return 1;
});
System.out.println(f.get());
System.out.println(f.isDone());//是否执行完
}
}
<a name="RBChZ"></a>
# 三、Future主要方法
```java
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
2.1、boolean cancel(boolean mayInterruptIfRunning);
取消异步正在执行的任务:如果一个异步任务的运行特别耗时,那么Future是允许对其进行取消操作的。
2.2、V get() throws InterruptedException, ExecutionException;
获取异步执行任务的结果:当异步任务被正常执行完毕,可以通过get方法或者其重载方法(指定超时单位时间)获取最终的结果。
四、任务执行错误
Runnable类型的任务中,run()方法抛出的异常(运行时异常)只能被运行它的线程捕获(有可能会导致运行线程死亡),但是启动运行线程的主线程却很难获得Runnable任务运行时出现的异常信息。我们可以通过设置UncaughtExceptionHandler
的方式来捕获异常,但是这种方式的确不够优雅,并且也无法精确地知道是执行哪个任务时出现的错误,Future则是通过捕获get方法异常的方式来获取异步任务执行的错误信息的,如下面的示例代码所示。
public static void main(String[] args) throws Exception{
ExecutorService executorService = Executors.newSingleThreadExecutor();
Future<Object> submit = executorService.submit(() -> {
throw new RuntimeException();
});
try{
Object o = submit.get();
}catch (Exception e){
System.out.println("捕获异常");
e.printStackTrace();
}
}
五、ExecutorService与Future
5.1、提交Runnable类型任务
Submit方法除了可以提交执行Callable类型的任务之外,还可以提交Runnable类型的任务并且有两种重载形式,具体如下。
//提交Runnable类型的任务并且返回Future,待任务执行结束后,通过该future的get方法返回的结果始终为null。
<T> Future<T> submit(Callable<T> task);
//前一个提交Runnable类型的任务虽然会返回Future,但是任务结束之后通过future却拿不到任务的执行结果,而通过该submit方法则可以。
<T> Future<T> submit(Runnable task, T result);
5.2、invokeAny
ExecutorService允许一次性提交一批任务,但是其只关心第一个完成的任务和结果,比如,我们要获取某城市当天天气情况的服务信息,在该服务中,我们需要调用不同的服务提供商接口,最快返回的那条数据将会是显示在APP或者Web前端的天气情况信息,这样做的好处是可以提高系统响应速度,提升用户体验,下面通过一个简单的例子来了解一下invokeAny的使用。
5.3、invokeAll
invokeAll方法同样可用于异步处理批量的任务,但是该方法关心所有异步任务的运行,invokeAll方法同样也是阻塞方法,一直等待所有的异步任务执行结束并返回结果。
六、Future的不足之处
无法被动接收异步任务的计算结果:虽然我们可以主动将异步任务提交给线程池中的线程来执行,但是待异步任务结束后,主(当前)线程无法得到任务完成与否的通知(关于这一点,5.2.4节中将会给出解决方案),它需要通过get方法主动获取计算结果。
间彼此孤立:有时某一个耗时很长的异步任务执行结束以后,你还想利用它返回的结果再做进一步的运算,该运算也会是一个异步任务,两者之间的关系需要程序开发人员手动进行绑定赋予,Future并不能将其形成一个任务流(pipeline),每一个Future彼此之间都是孤立的,但5.5节将要介绍的CompletableFuture就可以将多个Future串联起来形成任务流(pipeline)。
Future没有很好的错误处理机制:截至目前,如果某个异步任务在执行的过程中发生了异常错误,调用者无法被动获知,必须通过捕获get方法的异常才能知道异步任务是否出现了错误,从而再做进一步的处理。