一文搞懂Runnable、Callable、Future、FutureTask及应用
作者:赶路人儿
一般创建线程只有两种方式,一种是继承Thread,一种是实现Runnable接口。但是这两种创建方式有一个致命的缺点就是没有返回值,没返回值就让人很苦恼了啊。得用共享变量或者其他通信方式才能得到线程处理完的结果,就有点麻烦。
还有一般不提倡使用继承Thread来创建线程方式,因为Java只有单继承,不能继承多个。但是Runnable是接口,所以可以让你的实现类同时实现多个接口。而且之后要上线程池,如果之前你是用Runnable来实现的,那就可以直接传入Runnable给线程池进行管理了!
在Java1.5之后就有了Callable、Future。这二种可以提供线程执行完的结果!
接下来简单介绍下Runnable、Callable 、Future、 FutureTask。
Runnable
Runnable 是一个接口,简单就一个方法,实现run方法,在run方法里面编写你要执行的代码就行了,但是没有任务返回接口,并且无法抛出异常。
public interface Runnable { public abstract void run(); }
简单应用
Runnable runnable = new Runnable() { @Override public void run() { System.out.println("2333"); } }; Thread thread = new Thread(runnable); thread.start();
Callable
Callable也是一个接口,很简单就一个call方法,在call方法里面编写你要执行的代码就行了,返回的就是执行的结果了。和Runnable 差别就是它有返回的结果,并且可以抛出异常!一般配合ThreadPoolExecutor使用的。
public interface Callable<V> { V call() throws Exception; }
简单应用,它不能直接配合Thread 使用。
Callable<String> callable = new Callable<String>() { @Override public String call() throws Exception { return "2333"; } }; ExecutorService executor = Executors.newFixedThreadPool(1); Future<String> result = executor.submit(callable); System.out.println(result.get());
Future
Future也是一个接口,它可以对具体的Runnable或者Callable任务进行取消、判断任务是否已取消、查询任务是否完成、获取任务结果。如果是Runnable的话返回的结果是null(下面会剖析为什么Runnable的任务,Future还能返回结果)。接口里面有以下几个方法。注意两个get方法都会阻塞当前调用get的线程,直到返回结果或者超时才会唤醒当前的线程。
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; }
简单应用
Callable<String> callable = new Callable<String>() { @Override public String call() throws Exception { return "2333"; } }; ExecutorService executor = Executors.newFixedThreadPool(1); Future<String> result = executor.submit(callable); result.cancel(true); System.out.println(result.isCancelled());
FutureTask
因为Future只是一个接口,所以是无法直接用来创建对象使用的,因此就有了下面的FutureTask。 FutureTask不是接口了,是个class。它实现了RunnableFuture接口 public class FutureTask<V> implements RunnableFuture<V>
而RunnableFuture接口又继承了Runnable和Future public interface RunnableFuture<V> extends Runnable, Future<V>
因此它可以作为Runnable被线程执行,又可以有Future的那些操作。它的两个构造器如下
FutureTask(Callable<V> callable); FutureTask(Runnable runnable, V result);
简单应用
FutureTask<String> futureTask = new FutureTask<>(() -> "2333"); Thread T1 = new Thread(futureTask); T1.start(); String result = futureTask.get(); System.out.println(result);
线程池应用
一般情况我们在用多线程的时候都会上线程池,而且一般我们使用ThreadPoolExecutor来创建线程池,我的上篇文章已经讲述了ThreadPoolExecutor,这里再补充一下submit用法。
Future<?> submit(Runnable task); // 提交 Runnable 任务 <T> Future<T> submit(Callable<T> task); // 提交 Callable 任务 <T> Future<T> submit(Runnable task, T result); // 提交 Runnable 任务及结果引用
也就是说如果我们需要返回任务的执行结果我们就得调用submit方法而不是execute。 submit也不神秘就是封装了一下我们的任务再execute
public Future<?> submit(Runnable task) { if (task == null) throw new NullPointerException(); RunnableFuture<Void> ftask = newTaskFor(task, null); //转成 RunnableFuture,传的result是null execute(ftask); return ftask; } public <T> Future<T> submit(Runnable task, T result) { if (task == null) throw new NullPointerException(); RunnableFuture<T> ftask = newTaskFor(task, result); execute(ftask); return ftask; } public <T> Future<T> submit(Callable<T> task) { if (task == null) throw new NullPointerException(); RunnableFuture<T> ftask = newTaskFor(task); execute(ftask); return ftask; }
newTaskFor方法是new了一个FutureTask返回。 所以三个方法其实都是把task转成FutureTask,如果task是Callable,就直接赋值。如果是Runnable 就转为Callable再赋值
task是Callable的情况
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) { return new FutureTask<T>(callable); } public FutureTask(Callable<V> callable) { if (callable == null) throw new NullPointerException(); this.callable = callable; this.state = NEW; }
task是Runnable 的情况
// 按顺序看,层层调用 protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) { return new FutureTask<T>(runnable, value); } public FutureTask(Runnable runnable, V result) { this.callable = Executors.callable(runnable, result); //转 runnable 为 callable this.state = NEW; } // 以下为Executors中的方法 public static <T> Callable<T> callable(Runnable task, T result) { if (task == null) throw new NullPointerException(); return new RunnableAdapter<T>(task, result); } static final class RunnableAdapter<T> implements Callable<T> { //适配器 final Runnable task; final T result; RunnableAdapter(Runnable task, T result) { this.task = task; this.result = result; } public T call() { task.run(); return result; } }
看了源码就揭开了神秘面纱了,就是因为Future需要返回结果,所以内部task必须是Callable,如果task是Runnable 我就造个假,偷天换日,在Runnable 外面包个Callable马甲,返回的结果在构造时就写好。
如果是调用Future<?> submit(Runnable task);
提交任务,构造的时候就直接是RunnableFuture<Void> ftask = newTaskFor(task, null);
直接塞了个null。所以调用Future.get时,拿到的就是null。所以这样有什么用呢?就只用能来判断任务已经执行完毕,就类似于Thread.join。
这还有个<T> Future<T> submit(Runnable task, T result);
你可能会奇怪这有啥用啊,你传入一个result,Future.get的时候返回的不还是这个result嘛。是个没错,但是这样用就行了!
class Task implements Runnable{ //定义task,传入p Person p; Task(Person r){ // 通过构造函数传入 p this.p = p; } void run() { r.setSex("男"); // 可以修改p } } Person p = new Person(); p.setName("小明"); ExecutorService executor = Executors.newFixedThreadPool(1); Future<Result> future = executor.submit(new Task(p), p); Person person = future.get(); person.getSex();
这样就可以得到修改后的结果了!
总结
综上所述,知道了Runnable 和Callable 的区别,Callable 可以获得任务结果和抛出异常,Runnable 没结果没异常抛出。
Future可以很容易的获得异步执行的结果,并且对任务进行一些操控。并且get等待结果时会阻塞,所以当任务之间有依赖关系的时候,一个任务依赖另一个任务的结果,可以用Future的get来等待依赖的任务完成的结果。
FutureTask 就是具体的实现类,有Runnable 的特性又有Future的特性,内部包的是Callable ,当然也有接受Runnable 的构造器,只是会偷偷把Runnable 转成Callable 来实现能返回结果的方法。
到此这篇关于一文搞懂Runnable、Callable、Future、FutureTask及应用的文章就介绍到这了,更多相关Runnable Callable Future FutureTask内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!