Callable与Future模式
1.介绍
创建线程的2种方式:继承Thread,实现Runnable接口,这两种方式都有一个缺陷就是:在执行完任务之后,无法获取执行结果。
如果需要获取执行结果,就必须通过共享变量或者使用线程通信的方式达到效果。
Callable位于java.util.concurrent包下:
public interface Callable<V>{
V calls throws Exception;
}
Callable一般情况下是配合ExecutorService来使用的,在ExecutorService接口中声明了若干个submit方法的重载版本:
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
2.Future
Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。
public interface Future<V> {
boolean cancle(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get();
V get(long timeout, TimeUnit unit);
}
-
1.cancel()方法,用来取消任务,如果取消任务成功则返回true,如果任务取消失败则返回false。
参数mayInterruptIfRunning表示是否允许取消正在执行却没有执行完成的任务,如果设置为true,则表示可以取消正在执行过程中的任务。
如果任务已经完成,则无论mayInterruptIfRunning为true或false,肯定返回false.
如果任务还没有执行,则无论mayInterruptIfRunning为true或false,肯定返回true。
如果任务正在执行,若mayInterruptIfRunning设置为true,则返回true.若mayInterruptIfRunning设置为false,则返回false. -
2.isCancelled方法表示任务是否被取消成功,如果任务正常完成前被取消成功,则返回true。
-
3.isDone方法表示任务是否已经完成,若任务完成,则返回true;
-
4.get()方法用来获取执行结果,这个方法会产生阻塞,会一直等到任务执行完毕才返回;
-
5.get(long timeout, TimeUnit unit)用来获取执行结果,如果在指定时间内,还没获取到结果,就直接返回null。
Future提供了三种功能:
- 1.判断任务是否完成
- 2.能够中断任务
- 3.能够获取任务执行结果
Future只是一个接口,无法用来直接创建对象使用,因此有了FutureTask.
FutureTask实现了RunnableFuture接口,这个接口的定义如下:
public interface RunnableFuture<V> extends Runnable,Future<V> {
void run();
}
FutureTask可以由执行者调度,这一点很关键,它对外提供的方法基本上就是Future和Runnable接口的组合。
示例:
public class MyCallable implements Callable<String>{
private long waitTime;
public MyCallable(long waitTime){
this.waitTime = waitTime;
}
@override
public void call(){
Thread.sleep(waitTime);
return threadName;
}
}
public class FutureTaskExample{
public static void main(String[] args){
MyCallable callable1 = new MyCallable(1000);
MyCallable callable2 = new MyCallable(2000);
FutureTask<String> futureTask1 = new FutureTask<String>(callable1);
FutureTask<String> futureTask2 = new FutureTask<String>(callable2);
ExecutorService executor = Executors.newFixedThreadPool(2);
executor.execute(futureTask1);
executor.execute(futureTask2);
while(true){
try{
if(futureTask1.isDone() && futureTask2.isDone()){
sysout("Done");
executor.shutdown();
return;
}
if(!futureTask1.isDone()){
sysout("futureTask1 output:" + futureTask1)
}
sysout("waiting for FutureTask2 to complete");
String s = futureTask2.get(200L, TimeUnit.MILLSECONDS);
if(s != null){
sysout("FutureTask2 output:" + s);
}
}
}
}
}
3.Future模式
Future模式的核心在于:去除了主函数的等待时间,并使得原本需要等待
时间段可以用于处理其他业务逻辑。
Future模式:对于多线程,如果线程A要等待线程B的结果,那么线程A没必要等待B,直到B有结果,可以先拿到一个未来的Future,等B有结果后再取真实的结果。
Future模式的简单实现:
-
核心接口Data,代表客户端希望获取的数据。Future模式中,这个Data接口有两个重要的实现。分别是RealData——代表真实的数据,即最终要获得的,有价值的信息。另一个就是FutureData,是可以立即返回的,是用来提取RealData的一个契约。
-
FutureData实现了一个快速返回的RealData包装,作为RealData的虚拟实现,当使用FutureData的getResult()方法时,如果实际的数据没有准备好,那么就会阻塞,阻塞,等待RealData准备好并且注入到FutureData中,才会最终返回数据。
-
Data接口:
public interface Data{
String getResult();
}
- FutureData:
FutureData是Future模式的关键,实际上是真实数据RealData的代理,封装了获取RealData的等待过程。
public class FutureData implements Data{
protected RealData realData;
protected boolean isReady;
// 传入构造好的RealData;
public synchronized void setRealData(RealData realData){
if(isReady){
return;
}
this.realData = realData;
isReay = true;
// 真实数据已经注入,通知getResult方法可以拿到。
notifyAll();
}
// 等待RealData构造完成
public synchronized String getResult(){
while(!isReady){
// 一直等待,直到RealData注入,收到了通知
try:wait();
}
return realData.result;
}
}
- RealData
最终要用到的数据模型,构造很慢,使用sleep模拟这个过程。
public class RealData implements Data{
private final String result;
// 构造RealData是一个耗时的过程
public RealData(String string){
StringBuffer sb = new StringBuffer();
for(int i = 0; i < 10; i++){
sb.append(String + " ");
Thread.sleep(1000);
}
result = sb.toString();
}
@override
public String getResult(){
return result;
}
}
- Client:
client主要实现了获取FutureData,并且开启构造RealData的线程,并且在接受请求后,很快的返回FutureData.
public class Client{
public Data request(final String query){
final FutureData futureData = new FutureData();
// 启动一个单独的线程构造RealData对象
new Thread(() -> {
// 耗时进程
RealData realData = new RealData(query);
futureData.setRealData(realData);
}).start();
return futureData;
}
}
- 测试
main():
Client client = new Client();
Data data = client.request("java");
sysout("request over");
// 这里Sleep代替了对其他业务逻辑的处理,在主线程处理业务逻辑的时候,
// Client开启了一个线程构造RealData,从而充分利用了时间。
try:Thread.sleep(2000);
// 如果真实的数据已经构造完毕,那么这里会立即返回,但是如果真实的数据
// 没有构造完毕,那么这里仍然会阻塞。
sysout("RealData:" + data.getResult());
结果: request over:
RealData: java
4.JDK中的Future模式
public class MyRealData implements Callable<String>{
private String data;
public MyRealData(String data){
this.data = data;
}
// 构造真实的数据返回,这个过程可能是缓慢的
@override
public String call(){
StringBuffer sb = new StringBuffer();
for(int i = 0; i < 10; i++){
sb.append(data + " ");
}
try: Thread.sleep(100); // 模拟真实数据的缓慢构造
return sb.toString();
}
}
- 测试
public static void main(String[] args){
// 构造FutureTask,其中的Callable封装了实际数据的构造过程
FutureTask<String> futureTask = new FutureTask<>(new MyRealData("python"));
ExecutorService es = Executors.newFixedThreadPool();
// 执行FutureTask,相当于上例子中client.request(str)发送请求
// 这里会开启线程进行RealData的call执行
es.submit(futureTask);
sysout("request end.");
// 这里用Sleep代替了对其他业务逻辑的处理
// 在主线程处理业务逻辑的时候,线程池开启了一个线程进行真实数据的构造
try{
Thread.sleep(1000)
};
// 相当于上例子中的data.getResult(),取得call方法里的返回值
// 如果call方法没有执行完成,那么这里依旧会阻塞
sysout("Data:" + futureTask.get());
}