引言

上一篇文章,我们讲解了Thread和Runnable,它们分别代表线程和可执行任务。这篇文章,我们来看Executor、Callable和Future,通过这篇文章,我们先对这几个类有一个感性的认识,之后的文章中,我们再来看它们的使用。

Callable

Callable与Runnable类似,都是代表可执行任务。与Runnable不同的是,Callable会有返回值并且会抛出异常。
它的定义如下:

  1. public interface Callable<V> {}

它唯一的方法是call:

  1. V call() throws Exception;

这里的返回类型是V,也就是说在声明一个Callable时,我们就可以指定它的返回值类型。
我们讲Runnable时知道,Thread本身就实现了Runnable,启动一个线程会默认调用它的内部的Runnable的run方法,或者可以重写该run方法。也就是说,Thread和Runnable是紧密联系的,那么Callable在哪里用到了呢?稍后我们会看到。

Executor

Executor也是一个接口,它用来执行提交的Runnable任务。它只有一个方法:

  1. void execute(Runnable command);

可以看到该方法的参数是Runnable。也就是说,Runnable代表的是任务的执行。这样,线程、任务、任务的执行就完全解耦开了。那Runnable是怎样执行任务的呢?最简单的例子,我们可以这样实现Executor:

  1. public class SimpleExecutor implements Executor {
  2. @Override
  3. public void execute(Runnable command) {
  4. command.run();
  5. }
  6. public static void main(String[] args) {
  7. SimpleExecutor simpleExecutor = new SimpleExecutor();
  8. simpleExecutor.execute(new Runnable() {
  9. @Override
  10. public void run() {
  11. System.out.println("当前线程是"+Thread.currentThread().getName());
  12. }
  13. });
  14. }
  15. }

在这个例子中,我们实现了Executor接口,并且execute方法就是简单的调用Runnable的run方法。这是一个简单的方法调用,输出如下:

  1. 当前线程是main

也就是executor在当前线程中执行了任务。当然,我们也可以创建新的线程来执行任务:

  1. public class SimpleExecutor implements Executor {
  2. @Override
  3. public void execute(Runnable command) {
  4. new Thread(command).start();
  5. }
  6. public static void main(String[] args) {
  7. SimpleExecutor simpleExecutor = new SimpleExecutor();
  8. simpleExecutor.execute(new Runnable() {
  9. @Override
  10. public void run() {
  11. System.out.println("当前线程是"+Thread.currentThread().getName());
  12. }
  13. });
  14. }
  15. }

这个例子中,我们每次执行execute方法都会新创建一个线程来执行任务,这样就是一个简单的异步执行。输出如下:

  1. 当前线程是Thread-0

Executor只有execute方法来提供很简单的功能,它的子接口ExecutorService有更多的扩展功能,在看ExecutorService之前,我们先来看一下Future:

Future

Future也是一个接口,它用来代表一个异步计算的结果。它的声明如下:

  1. public interface Future<V> {}

它提供了用来获取任务结果以及其他的一些方法,我们重点看get方法:

  1. V get() throws InterruptedException, ExecutionException;
  2. V get(long timeout, TimeUnit unit)
  3. throws InterruptedException, ExecutionException, TimeoutException;

get方法用来获取任务的执行结果,如果任务已经执行完成,就会直接返回结果,否则线程就会阻塞直到任务执行完成。第二个get方法带有时间参数,用来指定最多等待任务执行的时间,如果在指定时间内任务没有执行完成,就会抛出TimeoutException。
稍后,我们会看到Future的使用示例。

ExecutorService

ExecutorService在Executor的基础上增加了以下几个方法:
image.png
我们重点关注两个submit方法:

  1. <T> Future<T> submit(Callable<T> task);
  2. <T> Future<T> submit(Runnable task, T result);

这两个方法都是用来提交一个任务。第一个的参数是上面我们说的Callable,第二个的参数是Runnable和代表返回值的T。它们的返回值都是Future。
上面在说Executor接口时,我们自己的SimpleExecutor类实现了这个接口,并重写了execute方法,因为该方法没有返回值,没有异常,所以很容易实现,只需要调用Runnable的run方法即可。但是ExecutorService的submit方法的返回值是Future,我们怎么去实现它的逻辑呢?或者说,ExecutorService的submit方法是怎样实现将Callable或者Runnable参数代表的任务执行,然后返回Future呢?这个我们下一篇文章来看。

小结

Callable、Future和ExecutorService的使用紧密相关,ExecutorService作为Executor的子接口,它提供了submit方法来执行任务,而任务就是Callable或者Runnable,返回值就是Future。这篇文章,我们需要重点理解这几个类在整个任务执行框架各自扮演的角色和作用,记住它们各自提供的重点方法。下一篇文章,我们会讲解submit方法的逻辑。