理解三种任务Runnable和Callable和FutureTask的用法
1.Runnable 和Callable和FutureTask的区别
相同点:都属于线程池中要被运行的任务;
不同点:
Runnable是无返回值的任务,可以在线程中使用
Callable是有返回值的任务 ,不可以在线程中使用
FutureTask是有返回值,而且更易于管理和控制的任务,不可以在线程中使用;
前两者通过查看他们类可以很清楚的知道
public interface Runnable {
/**这个任务运行完之后没有返回值*/
public abstract void run();
}
public interface Callable {
/**这个任务运行完之后返回泛型 V*/
V call() throws Exception;
}
而FutureTask稍微复杂一点,其实看看它的类结构 ,你也就明白了怎么回事
看到了吗,它也是一个Runnable的子类,这里还用到了一个Future类,其实想一下,Runnable是不可控制的任务,Future为这个任务提供了一套标准来管理任务;不信看看它的类
public interface Future {
/**取消这个任务执行,传递参数是true,表示停止正在执行的任务,否则,这行完这次任务*/
boolean cancel(boolean mayInterruptIfRunning);
/**任务是否被取消*/
boolean isCancelled();
/**任务是否完成*/
boolean isDone();
/**获取任务的返回结果*/
V get() throws InterruptedException, ExecutionException;
/**获取任务的结果,还没有完成就等待*/
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
2.Runnable和Callable和FutureTask的执行
线程池ExecutorService里面提交任务的方法submit有如下三个重载,
而在ExecutorService的抽象实现类AbstractExecutorService里面对着三个submit方法做了实现,你会发现他们都调用了Executor接口中的execute(Runnable command)方法;
至于这三个类的关系,看下图就知道,他们是继承关系
再来看看AbstractExecutorService类中三种submit方法源码的执行吧
public Future> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
public Future submit(Runnable task, T result) {
if (task == null) throw new NullPointerException();
RunnableFuture ftask = newTaskFor(task, result);
execute(ftask);
return ftask;
}
public Future submit(Callable task) {
if (task == null) throw new NullPointerException();
RunnableFuture ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
这三个方法都会:
1.先通过newTaskFor方法生成一个RunnableFuture的对象.
2.然后在执行execute方法.
1.生成newTaskFor方法过程
在看看newTaskFor方法是怎么回事;
protected RunnableFuture newTaskFor(Runnable runnable, T value) {
return new FutureTask(runnable, value);
}
protected RunnableFuture newTaskFor(Callable callable) {
return new FutureTask(callable);
}
原来它返回的是FutureTask类型,原来不论我们传递到submit方法中的是那种任务,先是转换成FutureTask类型;再来看看FutureTask的构造方法吧!
public FutureTask(Callable callable) {
if (callable == null)
throw new NullPointerException();
sync = new Sync(callable);
}
public FutureTask(Runnable runnable, V result) {
sync = new Sync(Executors.callable(runnable, result));
}
它构造出了一个 Sync对象,看看它的构造方法,
Sync(Callable callable) {
this.callable = callable;
}
传递进去的是Callable类型的,即使是Runable也会被转化为Callable类型,我们在看看 Excutors.call()这个方法,它是将Runable转化为一个Callable
public static Callable callable(Runnable task, T result) {
if (task == null)
throw new NullPointerException();
return new RunnableAdapter(task, result);
}
static final class RunnableAdapter implements Callable {
final Runnable task;
final T result;
RunnableAdapter(Runnable task, T result) {
this.task = task;
this.result = result;
}
public T call() {
task.run();
return result;
}
}
Excutors.call这个方法会先将生成一个 RunnableAdapter,而这个类中含有Runnable的应用;
到此 submit中提交的任务最终都会被转化为Callable类型的任务;
梳理一下 我们传递进来的不论是Runable类型还是Callable类型任务,都统一构造成FutureTask类型(只接受Callable类型参数),如果是Runable类型,而FutureTask构造内部会将它转为会Callable类型的变量传入FutureTask的构造;
2.execute方法的执行
到这里excute方法要执行的这个FutureTask对象,我们先想一下它又是什么?上面我们已经提到了,它呀实现了Runnaable接口,不就是一个线程吗?我们要执行的是一个线程!线程要看什么不就是run()方法?那么现在我们直接看看FutureTask类中实现的run方法不就是了;当然execute内部执行不看了,反正它是一个线程,是线程就要执行run()方法对吗?
看看FutureTask中的run()方法的源代码
public void run() {
sync.innerRun();
}
看看就它一行代码,什么也不说了;往下继续走吧!
void innerRun() {
if (!compareAndSetState(READY, RUNNING))
return;
runner = Thread.currentThread();
if (getState() == RUNNING) { // recheck after setting thread
V result;
try {
result = callable.call();
} catch (Throwable ex) {
setException(ex);
return;
}
set(result);
} else {
releaseShared(0); // cancel
}
}
看到那一行 result = callable.call()代码了吗,当线程处于运行状态的时候会调用它
submit提交任务的时候 FutureTask中持有Sync引用 ,而Sync中又持有Callable的引用,而此处的callable不就是我们的传递进来的Callable类型的对象,或者是Runnable类型的对象转换成的Callable吗;
现在好办了
1.如果是我们自己实现了Callable接口,那么此处就会直接调用我们overvide的call()方法;看代码示例
ExecutorService mExecutorService = Executors.newCachedThreadPool();
mExecutorService.submit(new Callable() {
@Override
public String call() throws Exception {
System.out.println("我直接实现了Callable接口");
return "111";
}
});
此时会直接调用自己的实现的call方法;打印
System.out.println的内容
2.那么我是实现了Runnable接口的情况,那么再看分析吧!
是Runable的话,会被Excutors.call方法转变成Callable对象使用;当此处调用result = callable.call()的时候,这个callable是转化来的。这个callable又是怎么回事?上面也提到了,这里再看一眼Excutors.call是怎么转化的;
public static Callable callable(Runnable task, T result) {
if (task == null)
throw new NullPointerException();
return new RunnableAdapter(task, result);
}
static final class RunnableAdapter implements Callable {
final Runnable task;
final T result;
RunnableAdapter(Runnable task, T result) {
this.task = task;
this.result = result;
}
public T call() {
task.run();
return result;
}
}
转化成的RunnableAdapter对象也是一个Callable的实现类; 这个时候会调用自己实现的call()方法;看看call()方法的内部task.run()这行代码 ,task会指向我们的Runable的对象;
3.那么如果我submit中传递进去的FutureTask呢!
按照我上面的Callable和Runnable套路分析,你会发现这全都是套路!在走一遍套路,先看一下例子代码;
FutureTask m1 = new FutureTask(
new Callable() {
@Override
public String call() throws Exception {
return null;
}
});
mExecutorService.submit(m1);
FutureTask m2 = new FutureTask(
new Runnable() {
@Override
public void run() {
}
}, null);
mExecutorService.submit(m2);
上面是FutureTask的两种构造;一个传递进去了Callable,另一个传递进去了Runable了;
分析
首先FutureTask实现了Runnable接口!
那么m1和m2他们都会被当成是Runnable参数构造成一个新的FutureTask任务,执行的时候会按照
2.那么我是实现了Runnable接口的情况,那么再看分析吧!这种情形分析的执行吧.
那么我这个m1和m2分别执行自己的run()方法,但是自己没有覆写run()方法,m1和m2都是FutureTask的对象。这时候你会发现
m1按照
1.如果是我们自己实现了Callable接口,那么此处就会直接调用我们overvide的call()方法;看代码示例 套路走!
m2按照
2.那么我是实现了Runnable接口的情况,那么再看分析吧!套路走!
是不是有点递归的感觉的,,,差点被绕晕了!
以上就是我对三种任务的理解,希望对大家有所帮助!