Java Callable和Future

时间:2020-01-09 10:35:12  来源:igfitidea点击:

在本文中,我们将看到并发API的两个有趣的功能,Callable和Future。

Java中的Callable

考虑一个计算量很大的场景,我们想将其拆分为多个子任务,这些子任务由多个线程执行,每个线程都在一部分任务上工作。一旦所有线程完成其任务,就可以合并部分结果以获取计算结果。
使用Runnable设计这种情况会很困难,因为Runnable不会返回结果。该缺陷由Java中的Callable填补,因为它可以返回结果并可能引发异常。

可回收接口

Java中的可调用接口具有单个方法调用,该方法可以计算结果并返回结果,或者在无法执行结果时引发异常。

public interface Callable<V> {
    V call() throws Exception;
}

因此,我们需要实现call()方法以提供必须由线程实现的任务作为异步计算。这是Callable实现的一个简单示例

Callable<String> callable = new Callable<String>() {
  public String call() {
    return "Value returned from Callable";
  }
};

由于Callable是功能性接口,因此也可以将其实现为lambda表达式。

Callable<String> callable = ()->{
  return "Value returned from Callable";
};

使用ExecutorService运行可调用任务

要执行Callable,使用ExecutorService的Submit()方法。

<T> Future <T> Submit(Callable <T> task)–提交可调用任务,该任务返回要执行的值,并返回表示任务的未决结果的Future。

提交可调用任务时,将在其自己的线程中异步执行该任务。目前尚不知道异步计算的结果何时可用,我们所知道的是它将在将来使用。因此,适当命名的接口Future表示可调用任务的返回值。

Java Callable and Future示例

这是一个简单的示例,显示了如何使用ExecutorService提交可调用任务以及如何使用Future获得返回值。

public class CallableDemo {
  public static void main(String[] args) {
    ExecutorService executor = Executors.newSingleThreadExecutor();
    Date date = new Date();
    System.out.println("Submitting callable task " + date);
    // submitting callable task
    Future<String> future = executor.submit(()->{
      TimeUnit.SECONDS.sleep(4);
      return "Value returned from Callable";
    });
    System.out.println("Submitted callable task " + new Date());
    // getting result 
    try {
      System.out.println("Returned value-" + future.get() + " at " + new Date());
    } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    } catch (ExecutionException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    executor.shutdown();
  }
}

输出:

Submitting callable task Tue Dec 04 11:18:05 IST 2018
Submitted callable task Tue Dec 04 11:18:05 IST 2018
Returned value-Value returned from Callable at Tue Dec 04 11:18:09 IST 2018

如我们所见,可调用任务被提交执行以在其自己的线程中执行,而主线程继续执行(第二个System.out在可调用提交后立即执行)。
然后,调用get方法以检索计算结果,因为get()是阻塞调用,因此如果需要,它将等待计算完成。

Java的Future

Future代表异步计算的结果。

Future接口提供了一些方法来检查计算是否完成,等待其完成以及检索计算结果。

  • cancel(bollean interruptFlag)–尝试取消执行此任务。

  • get()–必要时等待计算完成,然后检索其结果。

  • get(long timeout,TimeUnit unit)–必要时最多等待给定时间以完成计算,然后检索其结果(如果有)。

  • isCancelled()–如果此任务在正常完成之前被取消,则返回true。

  • isDone()–如果此任务完成,则返回true。

Callable 和 Future 示例

这是一个可调用和将来的示例,其中使用两个线程的池执行4个可调用任务。

public class CallableDemo {
  public static void main(String[] args) {
    // Pool of 2 threads
    ExecutorService executor = Executors.newFixedThreadPool(2);
    System.out.println("Submitting callable tasks " + new Date());
    Future<String> f1 = executor.submit(new MyCallable("Callable task-1"));
    Future<String> f2 = executor.submit(new MyCallable("Callable task-2"));
    Future<String> f3 = executor.submit(new MyCallable("Callable task-3"));
    Future<String> f4 = executor.submit(new MyCallable("Callable task-4"));
    System.out.println("Submitted callable task " + new Date());
                
    // getting result 
    try {
      // Calling get() method to get the future value
      System.out.println("Value for task-1 " + f1.get() + " at " + new Date());
      System.out.println("Value for task-2 " + f2.get() + " at " + new Date());
      while(!f3.isDone()) {
        System.out.println("Waiting for task-3 to complete " + f2.get());
        TimeUnit.MILLISECONDS.sleep(500);
      }
      System.out.println("Value for task-3 after it is completed " + f3.get() + " at " + new Date());
      System.out.println("Value for task-4 " + f4.get() + " at " + new Date());
    } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    } catch (ExecutionException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
      executor.shutdown();
  }
}

class MyCallable implements Callable<String> {
  String str;
  MyCallable(String str){
    this.str = str;
  }
  @Override
  public String call() throws Exception {
    System.out.println("In call method, thread name- " + Thread.currentThread().getName());
    TimeUnit.SECONDS.sleep(2);
    return str;
  }
}

输出:

Submitting callable tasks Tue Dec 04 11:47:23 IST 2018
Submitted callable task Tue Dec 04 11:47:23 IST 2018
In call method, thread name- pool-1-thread-1
In call method, thread name- pool-1-thread-2
In call method, thread name- pool-1-thread-2
In call method, thread name- pool-1-thread-1
Value for task-1 Callable task-1 at Tue Dec 04 11:47:25 IST 2018
Value for task-2 Callable task-2 at Tue Dec 04 11:47:25 IST 2018
Waiting for task-3 to complete Callable task-2
Waiting for task-3 to complete Callable task-2
Waiting for task-3 to complete Callable task-2
Waiting for task-3 to complete Callable task-2
Value for task-3 after it is completed Callable task-3 at Tue Dec 04 11:47:27 IST 2018
Value for task-4 Callable task-4 at Tue Dec 04 11:47:27 IST 2018

从输出中可以看到,线程池中的一个线程执行了两个可调用任务,而另一个线程执行了两个任务。在示例中,isDone()方法还用于检查提交的任务是否完成。