说到多线程编程,首先想到的大概是Thread和Runnable,自己平时用的比较多的也是这两个。不过他们有一个缺点就是无法返回执行结果。如果想要获取,就要使用一些其他的方法,比如线程间通信,总之比较麻烦。
Java在1.5开始引入了Executor框架的一系列相关的内容,包括的Callable和Future就解决了前面提到的无法返回结果的问题。Executors类还提供了一系列的方法来生成线程池,来提高多线程编程时的效率。
这篇文章就先记录一下关于Executor,Callable,Future和FutureTask的学习内容。
Executor
先来说说Executor,这是一个接口
public interface Executor {
/**
* Executes the given command at some time in the future. The command
* may execute in a new thread, in a pooled thread, or in the calling
* thread, at the discretion of the {@code Executor} implementation.
*
* @param command the runnable task
* @throws RejectedExecutionException if this task cannot be
* accepted for execution
* @throws NullPointerException if command is null
*/
void execute(Runnable command);
}
内容很简单,只有一个execute方法,参数是一个Runnable。从它的注释就可以看出来,Executor并没又规定一定会异步执行这个Runnable,而是取决于我们的实现。如果按照下面这样写
Executor executor = new Executor() {
@Override
public void execute(Runnable command) {
command.run();
}
};
executor.execute(new Runnable() {
@Override
public void run() {
}
});
显然,在调用execute方法后,Runnable的run方法就会马上在调用的线程中执行了。和直接执行Runnable的run方法没有什么区别。
Executor的作用就是将它的调用和执行方式分离开来,提交到同一个Executor的任务的执行方式必然是一致的。如果需要异步操作,也不需要每次去写new Thread再调用start。看下面代码
class MyExecutor implements Executor {
@Override
public void execute(Runnable command) {
new Thread(command).start();
}
}
这样的话,之后我只要调用MyExecutor.execute(runnable),就可以新建一个线程执行,并且可以复用,统一方式处理。
Callable
Callable也是一个接口,代码如下
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
其实他和Runnable很像,只不过把run方法改成了call,并且有了返回值,可以看到是一个泛型(泛型我还没有完全搞懂,之前以为很简单,看了Effective Java之后感觉又糊涂了。有时间要再研究一下),这样就可以把执行的结果返回出去。
Callable一般结合ExecutorService使用,后面会给出用法。
Future
这个接口代码注释比较长就不贴了,可以直接去看源码。它的作用就是结合Callable或者Runnable来使用,定义了五个方法
boolean cancel(boolean mayInterruptIfRunning);
取消执行,成功返回true,失败则返回false。如果任务已经完成了、被取消了或者因为一些原因无法取消,则会失败。如果任务尚未执行,再调用cancel后则永远不会再执行了。如果任务正在执行,就要看参数mayInterruptIfRunning,如果为true,就会中断执行;如果为false,则会执行完这个任务。
boolean isCancelled();
任务有没有被成功取消
boolean isDone();
任务是否完成
V get() throws InterruptedException, ExecutionException;
获取执行结果。这个方法会阻塞调用的线程直到任务完成并返回结果。
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
获取执行结果,同样会阻塞调用的线程,如果在给的时间内没有执行完,则会返回null。
这是一个接口,具体实现我们可以看一下FutureTask。
FutureTask
FutureTask实际上实现的是RunnableFuture,而RunnableFuture同时实现了Runnable和Future。实现了各个方法的逻辑,具体可以看源码。
例子
public class Test {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(5);
//1
FutureTask<Integer> futureTask = new FutureTask<>(new MyCallable());
// Future future = executor.submit(futureTask);
Future future = executor.submit(futureTask, 3);
//2
Future future1 = executor.submit(new MyCallable());
executor.shutdown();
try {
Log.d("testexecutor--", "future: " + future.get());
Log.d("testexecutor--", "futureTask:" + futureTask.get());
Log.d("testexecutor--", "future1: " + future1.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int result = 0;
for (int i = 0; i < 100; i++){
result += i;
}
return result;
}
}
}
定义了一个MyCallable继承了Callable,做的事情就是从0到99叠加,最后返回结果。然后使用ExecutorService生成了一个线程池(线程池的具体用法留到下篇文章),之后用了两种方法像里面添加任务,第一种是submit一个FutureTask,第二种是直接submit Callable,两者都可以达成像线程池中添加任务的目的。
区别在于通过Future获得结果的方式。我们通过代码也可以看得出,executor的submit其实有三种重载的方法
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
参数是Callable的方法也就是对应例子中第2种方法,这种方法通过submit方法返回的Future可以直接get到结果。而第1种方法中用到的两个submit的重载,需要通过传入的FutureTask的get来获取结果。调用参数为Runnable的submit返回的Future的get,得到的是null,而使用两个参数的第二种重载,get得到的就是第二个参数传入的内容。所以如果运行这个例子,打出来的log如下
future: 3
futureTask:4950
future1: 4950
为什么前两种submit的重载方法返回得到的Future不能直接调用get来获得结果呢?看源码可以知道,是源码中直接写死了这个返回,第一种submit就是直接返回null,而第二种就直接返回了第二个参数。这三种方法最后都会生成一个FutureTask对象,不同的是因为前两种传入的都是Runnable,所以调用的是这个构造及后面的流程
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;
}
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);
this.state = NEW; // ensure visibility of callable
}
这个callable方法最后会执行传入的runnable,并返回result。
而第三种submit的重载传入的是一个Callable
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
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; // ensure visibility of callable
}
直接返回了这个FutureTask对象。所以直接对这个submit返回的Future调用get,也就是和例子中第一种方法,对FutureTask调用get一样的,都是调用了FutureTask的run,在run中就会得到返回值了。
参考大神文章:
http://www.cnblogs.com/dolphin0520/p/3949310.html
http://www.iteye.com/topic/366591
http://blog.youkuaiyun.com/linghu_java/article/details/17123057
659

被折叠的 条评论
为什么被折叠?



