前言
本文旨在简单讲解Runnable
、Callable
、FutureTask
这几个线程执行相关的接口和类。为后面FutureTask源码讲解作铺垫。
Runnable
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
我们知道创建线程有两种方式:
- override掉
Thread
的run方法:
new Thread() {
@Override
public void run() {
int count = 0;
for(int i = 1;i <= 100;i++)
count += i;
}
}.start();
看过Thread
源码都知道,我们调用Thread#start()
后,会创建一个新线程来执行这个Thread
的run方法。但上面这种执行者和执行task绑定在一起了,不灵活。
- 传递一个
Runnable
对象给Thread
:
new Thread(new Runnable(){
@Override
public void run() {
int count = 0;
for(int i = 1;i <= 100;i++)
count += i;
}
}).start();
这样,通过创建一个Runnable
匿名内部类对象,可以达到同样的效果,但是却把执行者和执行task分开了。
public class Thread implements Runnable {
/* What will be run. */
private Runnable target;
@Override
public void run() {
if (target != null) {
target.run();
}
}
}
从Thread
源码可以看到,当没有override掉run方法时,run方法将执行持有的Runnable
对象的run方法。简单的说,就是套娃。
public class test5 {
public static Runnable task = new Runnable() {
@Override
public void run() {
int count = 0;
for(int i = 1;i <= 100;i++)
count += i;
}
};
public static void main(String[] args) throws InterruptedException {
new Thread(task).start();
new Thread(task).start();
}
}
将执行者和执行task分开是有好处,上例就体现了两个执行者可以执行同一个task。
Callable
与Runnable相比,Callable接口有些不同之处:
- Runnable接口没有返回值,Callable接口有返回值。又因为是返回值是泛型,所以任何类型的返回值都支持。
- Runnable接口的run方法没有
throws Exception
。这意味着,Runnable不能抛出异常(子类不能抛出比父类更多的异常,但现在Runnable的run方法本身没有抛出任何异常);Callable接口可以抛出异常。
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
但注意,Thread
并没有一个构造器可以接受Callable
参数的,而且Thread
也没有一个Callable
类型成员的。
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
看来要想使用Callable
还得依靠别的东西。
FutureTask
先回答上面的问题,要想使用Callable
还得依靠FutureTask
,虽然这里暂且看不出来FutureTask
和Callable
的关系。
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
原来是FutureTask
的构造器可以接受一个Callable
对象,这就把这二者串起来了,而FutureTask
本身又是一个Runnable
,这说明可以把FutureTask
传递给Thread
对象的构造器。
public class test5 {
public static void main(String[] args) throws InterruptedException {
FutureTask<Integer> result = new FutureTask<Integer>(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int count = 0;
for(int i = 1;i <= 100;i++)
count += i;
return count;
}
});
new Thread(result).start();
try {
System.out.println(result.get());
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
/*output:
5050
*/
上面例子给出了FutureTask
的用法,看起来是线程通过FutureTask
对象间接调用到了Callable
的call方法。注意,调用result.get()
时主线程会阻塞一会直到call方法执行完毕。
别看这个类图稍微复杂,其实RunnableFuture
就是将Future
和Runnable
合成一个新接口而已,但没有增加任何一个新方法。Runnable
我们已经看过了,看看Future
吧:
public interface Future<V> {
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
}
Future被设计为一个可以异步获得task执行结果的接口。
get()
。获得task执行结果,如果task已经执行完,马上返回执行结果;如果task未执行完毕,则阻塞直到执行完毕。get(long timeout, TimeUnit unit)
。上一个函数的超时版本,阻塞直到 执行完毕或超时。cancel(boolean mayInterruptIfRunning)
。尝试取消task,返回值代表取消操作成功。isCancelled()
。判断一个task已经被取消了。取消一定是task没有执行完毕前就被取消,也包括根本没有执行就被取消。isDone()
。如果一个任务已经结束, 则返回true。返回true有三种情况:normal termination
。正常执行完毕。an exception
。执行过程抛出异常。cancellation
。task被取消。
当然,一般我们是配合线程池来使用Callable
:
package com.xxx.future;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class NoFuture {
public static class Tasker implements Runnable{
@Override
public void run(){
System.out.println("ah...");
}
}
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(3);
for(int i=0;i<5;i++){
service.submit(new Tasker());
}
service.shutdown();
}
}
但其实线程池的做法也是构造一个FutureTask
。
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
return new FutureTask<T>(callable);
}
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}