Java 多线程之Callable Future以及FutureTask
在以前的文章中介绍过创建线程的两种方式:一种直接继承Thread类,另外一种就是实现Runnable接口。这两种方式也是从接触Java以来接触到的最常见的创建线程的方式,但是这两种方式创建线程都有一个缺陷:在执行时完成后无法返回执行结果,就是没有返回值,这也是最致命的缺陷。如果真的需要执行结果,一般情况下或者使用一个共享变量或者线程间通信,使用相当麻烦。除此之外,创建的线程无法抛出任何的异常。
从JDK1.5开始,又引入了另外一种创建线程的方式,使用Callable和Future或者FutureTask,Callable接口类似于Runnable,两者都是为那些其实例可能被另一个线程执行的类设计的。但是Runnable不会返回结果,并且无法抛出经过检查的异常。
Callable以及相关类的简单介绍
首先看一下Callable与Runnable,Runnable接口位于java.lang包下面,只有一个run()方法,方法无返回值。
public interface Runnable { public abstract void run(); }
Callable也是一个接口类型,该接口位于java.util.concurrent包下,接口中只有一个方法call(),方法有返回值,返回值定义为Callable的泛型类型,并且抛出一个Exception。
public interface Callable{ V call() throws Exception; }
虽然Callable中定义的call()方法有返回值,但是在多线程开发中很少直接使用call()方法来获取运行结果,而是使用Future,跟Runnable中run()方法使用类似,在多线程开发中不会直接调用run方法。线程开发中一个常见的问题就是真正启动一个线程是run()方法还是start()方法。
Callable和Runnable的区别如下:
- Callable定义的方法是call(),而Runnable定义的方法是run()。
- Callable的call()方法可以有返回值,而Runnable的run()方法不能有返回值。
- Callable的call()方法可抛出异常,而Runnable的run()方法不能抛出异常。
在多线程开发中使用Callable一般是配合线程池ExecutorService的使用,在该接口中定义了submit()方法,该方法返回一个Future对象,方法如下:
public interface ExecutorService extends Executor { /** * Submits a value-returning task for execution and returns a * Future representing the pending results of the task. The * Future's {@code get} method will return the task's result upon * successful completion. * ** If you would like to immediately block waiting * for a task, you can use constructions of the form * {@code result = exec.submit(aCallable).get();} * *
Note: The {@link Executors} class includes a set of methods * that can convert some other common closure-like objects, * for example, {@link java.security.PrivilegedAction} to * {@link Callable} form so they can be submitted. * * @param task the task to submit * @param
the type of the task's result * @return a Future representing pending completion of the task * @throws RejectedExecutionException if the task cannot be * scheduled for execution * @throws NullPointerException if the task is null */ Future submit(Callable task); Future submit(Runnable task, T result); Future> submit(Runnable task); }
线程池ExecutorService部分并不在本文讨论的范畴之内,后续会另起博文介绍。下面是SDK中对Future的简要介绍。
Future表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并获取计算的结果。计算完成后只能使用 get 方法来获取结果,该方法是阻塞方法。取消则由 cancel方法来执行。还提供了其他方法,以确定任务是正常完成还是被取消了。一旦计算完成,就不能再取消计算。如果为了可取消性而使用 Future 但又不提供可用的结果,则可以声明 Future> 形式类型、并返回 null 作为底层任务的结果。
public interface Future{ boolean cancel(boolean mayInterruptIfRunning); boolean isCancelled(); boolean isDone(); V get() throws InterruptedException, ExecutionException; V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException; }
在ExecutorService中,所有的submit()方法都会返回一个Future实例,从而将Callable或者Runnable提交给Executor,并得到一个Future用于获取执行结果或者取消任务。
由于Future中定义的get()方法是一个阻塞方法,所以开发过程中除非必要否则Future一定要在子线程中获取执行结果,此处是个人意见。get()方法的返回取决于任务的状态(未开始、正在运行、已经完成),如果已经完成则会返回一个结果或者抛出一个Exception,如果正在运行,则get()方法会阻塞知道方法完成,如果任务抛出了异常,那么get()方法会将该异常封装为ExecutionException并重新抛出,如果任务已经取消了,那么get()方法也会抛出异常,该异常是CallcellationException。如果抛出了ExecutionException,可以使用getCause来获取封装的初始异常。
上面介绍了Runnable和Callable都是接口,而且方法定义中一个是无返回值的run方法,一个是有返回值的call方法,这种涉及在使用的时候兼容性则成了很大问题,如果后续定义一个工具类,在工具类某些情况使用的是run()方法,某些情况下使用call方法则更为妥当。事实上Java考虑到了这种情况,有一个即实现了Runnable接口又实现了Callable接口的类FutureTask。
在线程池中除了submit()方法,还有一个常用的void execute(Runnable command)
方法,因此FutureTask也可以交给execute()方法执行,对于Android开发者来说AsyncTask这个类一定都不陌生,该类中就是使用了FutureTask,更详细的可以查看源码。
下面FutureTask的定义,它实现了RunnableFuture接口,而实际上RunnableFuture实现了Runnable和Future接口。
public class FutureTaskimplements RunnableFuture { ... public FutureTask(Callable callable) { ... } public FutureTask(Runnable runnable, V result) { ... } } public interface RunnableFuture extends Runnable, Future { /** * Sets this Future to the result of its computation * unless it has been cancelled. */ void run(); }
Callable使用示例
Callable结合Future使用
首先实现一个Callable接口,在call方法中实现累加逻辑,并且每累加一次就睡眠500ms。
public class MyTask implements Callable{ int sum=0; @Override public Integer call() throws Exception { System.out.println("Thread:"+Thread.currentThread().getName()); for(int i=0;i<5;i++){ sum+=i; Thread.sleep(500); } return sum; } } ExecutorService executor = Executors.newSingleThreadExecutor(); MyTask call = new MyTask(); Future future = executor.submit(call); executor.shutdown(); System.out.println("main thread"); try { System.out.println(future.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } System.out.println("all end");
Callable结合FutureTask使用
这里跟Future不同的地方就是我们不需要通过ExecutorService的submit()方法的返回获取一个Future了,而是直接在new一个FutureTask的时候讲Future传入即可,然后通过FutureTask的get()方法既可以获取到计算结果。
ExecutorService executor = Executors.newSingleThreadExecutor(); MyTask call = new MyTask(); FutureTasktask = new FutureTask (call); executor.submit(task); executor.shutdown(); System.out.println("main thread"); try { System.out.println(task.get()); } catch (Exception e) { e.printStackTrace(); } System.out.println("all end");
由于FutureTask也实现了Runnable接口,所以FutureTask也可以不通过线程池构建一个异步执行任务,只需要按照Runnable新建并启动一个线程的思路实现即可。
MyTask call = new MyTask(); FutureTasktask = new FutureTask (call); Thread thread=new Thread(task); thread.start(); System.out.println("main thread"); try { System.out.println(task.get()); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } System.out.println("all end");
结束语
虽然Callable可以有返回值并且可以抛出异常,甚至与Future结合在一起使用还可以很好的控制一个任务的生命周期,但是它是一个阻塞方法,这也是Callable的一个很大弊端。平常开发中处理阻塞任务最常用的一个处理方式就是使用回调,这样就可以在事件发生时执行任何想要的代码。但是回调的缺点是,在处理回调事件时,代码会变得凌乱。而且回调特别难调试,因为控制流与应用程序中的代码顺序不匹配。为了解决这种可以说是致命弊端,Java8提供了CompletableFuture同时支持阻塞和非阻塞的事件处理方法,包括常规回调。