一、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 {

  1. FutureTask<Integer> task = new FutureTask<>(()->{
  2. TimeUnit.MILLISECONDS.sleep(500);
  3. return 1000;
  4. }); //new Callable () { Integer call();}
  5. new Thread(task).start();
  6. System.out.println(task.get()); //阻塞
  7. //*******************************
  8. ExecutorService service = Executors.newFixedThreadPool(5);
  9. Future<Integer> f = service.submit(()->{
  10. TimeUnit.MILLISECONDS.sleep(500);
  11. return 1;
  12. });
  13. System.out.println(f.get());
  14. System.out.println(f.isDone());//是否执行完
  15. }

}


<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方法的异常才能知道异步任务是否出现了错误,从而再做进一步的处理。

七、Google Guava的Future