多线程中最常用的是Runnable接口,定义了一次任务执行的内容。但是Runnable接口无法得到返回值,而且也无法让执行端捕获到异常。所以就有了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;
}
这是Callable接口的定义。
可以看到call方法可以抛出异常或者返回一个值。但是执行端该怎么处理呢?我们一旦起了一个线程,就需要得到返回值,但是这个返回值什么时候返回并不知道,因为是异步的,而且是得到返回值还是捕获了异常也不清楚,所以情况还是比较复杂的。这些都需要进行进一步的封装,也就是Future接口。个人认为,Future接口的存在是为了封装对任务执行情况的获取。主要有:
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
因为返回结果不确定何时返回,所以获取是一个阻塞式的操作,只有任务执行完了才返回,否则阻塞。Future接口正式干这个事情的。除了阻塞的get方法外,还有判断知否执行完的方法,相当于提供了一种非阻塞式的获取,我们可以在get前判断一下,如果执行完了再get。调用get时,因为可能爆出异常,所以要用try catch。
所以说Future和Callable是搭配在一起使用的。
使用的方法有两种
第一种,与线程池搭配。
那么在任务执行处就可以接收到一个Future,然后我们可以通过Future来获取结果。
public static void test1(){
ExecutorService pool = Executors.newCachedThreadPool();
Future<String> result = pool.submit(new Callable<String>() {
@Override
public String call() throws Exception {
Thread.sleep(2000);
return "ok";
}
});
try {
System.out.println(result.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
第二种,非线程池。那么也就是使用一个Thread来启动。但是Thread只能接受一个Runnable的接口,而且Runnable执行完start方法以后并不能返回任何值,所以此时应对这种情况,又封装了一个新的类TutureTask,该类同时继承了Future和Runnable接口,所有我们可以把它传给一个Thread,而且还可以通过它来获取返回值。其内部自然会有一个Callable的引用。本质上可以把FutureTask看成是一种扩展了的Runnable接口,内部维护了执行的情况,可以让我们得到返回值。
public static void test2(){
FutureTask<String> task = new FutureTask<>(new Callable<String>() {
@Override
public String call() throws Exception {
return "ok";
}
});
Thread t = new Thread(task);
t.start();
try {
System.out.println(task.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
这样就搞清楚了Callable,Runnable,Future和FutureTask接口的作用。