java线程获取结果Callable、Future、FutureTask
一、提个问题
当我们创建一个线程时,我们想获取线程运行完成后的结果,是否可以用回调的方法来实现?
答案是肯定的,可以。例如:
//定义一个回调接口
interface Callable {
void call(int num);
}
public class FutureTest {
public static void main(String[] args) {
//创建回调对象
Callable callable = new Callable() {
@Override
public void call(int num) {
System.out.println("线程运行结果值 num=="+num);
}
};
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程"+Thread.currentThread().getName()+" 开始");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//调用回调方法 把结果传出去
callable.call(100);
System.out.println("线程"+Thread.currentThread().getName()+" 结束");
}
}, "t1").start();
System.out.println("主线程结束");
}
}
运行结果:
主线程结束
线程t1 开始
线程运行结果值 num==100
线程t1 结束
这种方式的实现有三个缺点:
1、必须要创建回调接口。而且线程运行中可能产生异常,那么回调接口至少包含成功回调和错误回调两个方法。
2、当线程运行后,只能等到线程回调接口,本身我们没有办法进行取消操作。
3、如果要重复获取同样线程运行结果的值,还是只能重新运行线程。当然你也可以使用一个变量缓存结果值。
那么有没有一种优化的方式呢?java中提供了Future与Callable的模式。
二、理解 Runnable、Callable与Future
先看一下定义
Runnable
Runnable它只有一个run()函数,用于将耗时操作写在其中,该函数没有返回值。然后使用某个线程去执行该Runnable即可实现多线程,Thread类在调用start()函数后就是执行的是Runnable的run()函数.
public interface Runnable {
/*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
Callable
Callable中有一个call()函数,但是call()函数有返回值,而Runnable的run()函数不能将结果返回给客户程序。
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 与Runnable的区别
1、Callable定义的方法是call,而Runnable定义的方法是run。
2、Callable的call方法可以有返回值,而Runnable的run方法不能有返回值。
3、 Callable的call方法可抛出异常,而Runnable的run方法不能抛出异常
Future
Executor就是Runnable和Callable的调度容器,Future就是对于具体的Runnable或者Callable任务的执行结果进行
取消、查询是否完成、获取结果、设置结果操作进行的再次封装。注意其get方法会阻塞,直到任务返回结果。
public interface Future<V> {
/**
* Attempts to cancel execution of this task. This attempt will
* fail if the task has already completed, has already been cancelled,
* or could not be cancelled for some other reason. If successful,
* and this task has not started when <tt>cancel</tt> is called,
* this task should never run. If the task has already started,
* then the <tt>mayInterruptIfRunning</tt> parameter determines
* whether the thread executing this task should be interrupted in
* an attempt to stop the task.
*/
boolean cancel(boolean mayInterruptIfRunning);
/**
* Returns <tt>true</tt> if this task was cancelled before it completed
* normally.
*/
boolean isCancelled();
/**
* Returns <tt>true</tt> if this task completed.
*
*/
boolean isDone();
/**
* Waits if necessary for the computation to complete, and then
* retrieves its result.
*
* @return the computed result
*/
V get() throws InterruptedException, ExecutionException;
/**
* Waits if necessary for at most the given time for the computation
* to complete, and then retrieves its result, if available.
*
* @param timeout the maximum time to wait
* @param unit the time unit of the timeout argument
* @return the computed result
*/
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
Future<V>接口是用来获取异步计算结果的,说白了就是对具体的Runnable或者Callable对象任务执行的结果进行获取(get()),取消(cancel()),判断是否完成等操作
在Future接口中声明了5个方法,下面依次解释每个方法的作用:
1、cancel方法 用来取消任务,如果取消任务成功则返回true,如果取消任务失败则返回false。
参数mayInterruptIfRunning表示是否允许取消正在执行却没有执行完毕的任务,如果设置true,则表示可以取消正在执行过程中的任务。
如果任务已经完成,则无论mayInterruptIfRunning为true还是false,此方法肯定返回false,即如果取消已经完成的任务会返回false;
如果任务正在执行,若mayInterruptIfRunning设置为true,则返回true,若mayInterruptIfRunning设置为false,则返回false;如果任务还没有执行,则无论mayInterruptIfRunning为true还是false,肯定返回true。
2、isCancelled方法 表示任务是否被取消成功,如果在任务正常完成前被取消成功,则返回 true。
3、isDone方法 表示任务是否已经完成,若任务完成,则返回true;
4、get()方法 用来获取执行结果,这个方法会产生阻塞,会一直等到任务执行完毕才返回;
5、get(long timeout, TimeUnit unit) 用来获取执行结果,如果在指定时间内,还没获取到结果,就直接返回null。
也就是说Future提供了三种功能:
1)判断任务是否完成;
2)能够中断任务;
3)能够获取任务执行结果。
因为Future只是一个接口,所以是无法直接用来创建对象使用的,因此就有了下面的FutureTask
FutureTask
看一下FutureTask的实现类
public class FutureTask<V> implements RunnableFuture<V>
说明FutureTask类实现了RunnableFuture接口,我们看一下RunnableFuture接口的实现
public interface RunnableFuture<V> extends Runnable, Future<V> {
/**
* Sets this Future to the result of its computation
* unless it has been cancelled.
*/
void run();
}
可以看出RunnableFuture继承了Runnable接口和Future接口,而FutureTask实现了RunnableFuture接口。所以FutureTask既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。
另外FutureTaslk还可以包装Runnable和Callable<V>, 由构造函数注入依赖
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
上面代码块可以看出:
Runnable注入会被Executors.callable()函数转换为Callable类型,即FutureTask最终都是执行Callable类型的任务
因此FutureTask是Future也是Runnable,又是包装了的Callable( 如果是Runnable最终也会被转换为Callable )。
Callable 和 Future接口的区别
1、Callable规定的方法是call(),而Runnable规定的方法是run().
2、Callable的任务执行后可返回值,而Runnable的任务是不能返回值的。
3、call()方法可抛出异常,而run()方法是不能抛出异常的。
4、运行Callable任务可拿到一个Future对象, Future表示异步计算的结果。
5、它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。
6、 通过Future对象可了解任务执行情况,可取消任务的执行,还可获取任务执行的结果。
7、Callable是类似于Runnable的接口,实现Callable接口的类和实现Runnable的类都是可被其它线程执行的任务。
三、解决问题
搞清楚了概念我们就来解决文章开头提出的问题
1、使用Callable+Future获取执行结果
package com.demo.test;
import java.util.concurrent.Callable;
//定义一个任务
public class Task implements Callable<Integer>{
@Override
public Integer call() throws Exception {
System.out.println("子线程在进行计算");
Thread.sleep(3000);
int sum = 0;
for(int i=0;i<100;i++)
sum += i;
return sum;
}
}
package com.demo.test;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class CallableTest {
public static void main(String[] args) {
//创建线程池
ExecutorService executor = Executors.newCachedThreadPool();
//创建Callable对象任务
Task task = new Task();
//提交任务并获取执行结果
Future<Integer> result = executor.submit(task);
//关闭线程池
executor.shutdown();
try {
Thread.sleep(1000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
System.out.println("主线程在执行任务");
try {
if(result.get()!=null){
System.out.println("task运行结果"+result.get());
}else{
System.out.println("未获取到结果");
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println("所有任务执行完毕");
}
}
2、使用Callable+FutureTask获取执行结果
package com.demo.test;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
public class CallableTest1 {
public static void main(String[] args) {
//第一种方式
ExecutorService executor = Executors.newCachedThreadPool();
Task task = new Task();
FutureTask<Integer> futureTask = new FutureTask<Integer>(task);
executor.submit(futureTask);
executor.shutdown();
//第二种方式,注意这种方式和第一种方式效果是类似的,只不过一个使用的是ExecutorService,一个使用的是Thread
// Task task = new Task();
// FutureTask<Integer> futureTask = new FutureTask<Integer>(task);
// Thread thread = new Thread(futureTask);
// thread.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
System.out.println("主线程在执行任务");
try {
if(futureTask.get()!=null){
System.out.println("task运行结果"+futureTask.get());
}else{
System.out.println("future.get()未获取到结果");
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println("所有任务执行完毕");
}
}