首页 > 技术文章 > Future模式

alix-1988 2020-01-10 20:20 原文


Future模式是多线程开发中常见的设计模式,它的核心思想是异步调用。对于Future模式来说,它无法立即返回你需要的数据,但是它会返回一个契约,将来你可以凭借这个契约去获取你需要的信息。

 

这是传统的同步方法,调用一段耗时的程序。当客户端发出call请求,这个请求需要很长的一段时间才会返回,客户端一直在等待直到数据返回随后再进行其他任务的处理。

而使用Future模式:

 

这个模型展示了一个广义的Future模式的实现,从Data_Future对象可以看到虽然call本身任然需要一段很长时间处理程序。但是服务程序并不等数据处理完成便立即返回客户端一个伪造的数据(如:商品的订单,而不是商品本身),实现了Future模式的客户端在得到这个返回结果后并不急于对其进行处理而是调用其他业务逻辑,充分利用等待时间,这就是Future模式的核心所在。在完成其他业务处理后,最后再使用返回比较慢的Future数据。这样在整个调用中就不存在无谓的等待,充分利用所有的时间片,从而提高了系统响应速度。

Future模式的简单实现

Funture模式的实现中有一个核心接口Data,这是客户端希望获取的数据。这个Data接口在Funture模式中有两个重要的实现,分别是RealDate(真实数据),FuntureDate,它是用来提取RealData的一个“订单”(可以立即返回得到)。

Data接口:

package thread.futrue;

public interface Data {

String getResult();
}
FutureData实现了一个快速返回的RealData包装。因此它会快速返回,如果当使用FutrueData的getResult()方法时实际数据没有准备 好,那么程序就会阻塞,等待RrealData准备好数据并注入到FutureData中才最终返回数据。

注意:FutureData是Funture模式的关键,它是真实数据RealData的代理,封装了获取RealData的等待过程。

package thread.futrue;

public class FutureData implements Data {

protected RealData realData = null;

protected boolean isReady = false;

// 进行同步控制
public synchronized void setResult(RealData realData) {
if (isReady) {
return;
}
System.out.println("FutureData.setResult()");
this.realData = realData;
isReady = true;
notifyAll();

}

// 实际调用返回RealDate的数据
@Override
public synchronized String getResult() {
while (!isReady) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("FutureData.getResult()");
return realData.result;
}
}
RealData是最终需要使用的数据模型,它会进行较为耗时的操作,这里用sleep()函数来模拟这个过程。

package thread.futrue;

public class RealData implements Data{

protected final String result;

public RealData(String s) {
StringBuffer sb = new StringBuffer();

for (int i = 0; i < 5; i++) {
sb.append(s);
System.out.println(s.toString());
try {
//模拟构造时间比较长
Thread.sleep(1000);
} catch (InterruptedException e) {

}

}

System.out.println("RealData.RealData()");
result = sb.toString();
}

@Override
public String getResult() {
return result;
}
}
最后就是客户端代码:

package thread.futrue;

public class Client {
public Data request(final String queryStr){
//返回伪数据
final FutureData futureData = new FutureData();
//开启线程构造真实数据
new Thread(){
public void run(){
RealData realData = new RealData(queryStr);
System.out.println("要调用futureData");
futureData.setResult(realData);
}
}.start();
//返回伪数据,等待真实数据加载
return futureData;
}

public static void main(String[] args) {
Data data = new Client().request("123456");
System.out.println("main:"+data);
System.out.println("main:"+data.getResult());
}
}
运行后:

 

启动后它会立即返回一个futureData对象作为“订单”,然后在request方法中它会启动一个线程作为获取真实数据的业务处理,这里面首先会调用realData作为真实数据的获取,在主函数这会调用futureData的getResult来获取数据,如果真实数据还未处理完则会调用wait()方法进行等待,直到调用setResult方法将值放入它内部的成员对象中并且调用notifyAll方法。最后getResult方法返回真实数据并输出。

JDK中的Future模式

在JDK中Future模式有一套完整的实现。

 

Future接口类似于上面的“订单”接口或者说契约。通过它可以得到真实的数据。RunnableFuture继承了Future和Runnable接口,其中run方法用于构造真实的数据。它有个具体的实现类:FuntureTask类。FuntureTask有个内部类Sync,一些实质性的工作会委托Sync实现,而Sync类最终会调用Callable接口完成实际数据的组装工作。

Callable接口只有一个call()方法,它会返回需要构造的实际数据。Callable接口也是和Future框架和应用程序之间的重要接口。如果要实现自己的业务系统,通常需要实现自己的Callable对象。此外FutureTask类也与应用密切相关,通常情况下我们会使用Callable实例构造一个FutureTask实例并提交给线程池。

下面演示这个内置的Future模式的使用:

package thread.futrue;

import java.util.concurrent.Callable;

public class JdkRealData implements Callable<String>{

private String reaString;

public JdkRealData(String reaString) {
super();
this.reaString = reaString;
}

@Override
public String call() throws Exception {

StringBuffer sb = new StringBuffer();

for (int i = 0; i < 5; i++) {
sb.append(reaString);
System.out.println(sb.toString());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO: handle exception
}

}

return sb.toString();
}

}
这串代码实现了Callable接口,它的call()方法会构造我们需要的真实数据并返回,这个过程会有点缓慢所以我用Thread.sleep来进行模拟。

package thread.futrue;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;

public class TestMain {
public static void main(String[] args) throws InterruptedException, ExecutionException {
// 构造FutureTask
FutureTask<String> future = new FutureTask<>(new JdkRealData("1111"));
ExecutorService exe = Executors.newFixedThreadPool(1);
// 执行FutureTask,相当于上个例子中的request方法发送请求
// 在这里开启线程进行JdkRealData的call()方法执行
exe.submit(future);

System.out.println("请求完毕");

try {
// 这里可以进行额外操作,这里用sleep代替其他业务操作
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 相当于上个例子中的getResult()方法,娶个call方法的返回值
// 如果此时call()方法没有执行完成则会继续等待
System.out.println("最后的数据:" + future.get());
}
}
最后的返回值:

 

这串代码首先会构造一个FuntreTask对象的实例,表示这个任务有返回值。构造FutureTask时使用了Callable接口,告诉FutureTask我们需要的数据的生成方式,然后通过submit将FutureTask提交给线程池。然后可以进行其他的业务操作最后他会通过Future.get()这个方法得到最后的值。

除此之外Future接口还提供了一些简单的控制功能:

public boolean cancel(boolean mayInterruptIfRunning) ;//取消任务
public boolean isCancelled();//是否已经取消
public boolean isDone();//是否已经完成
public V get() throws InterruptedException, ExecutionException;//取得返回对象
public V get(long timeout, TimeUnit unit);//取得返回对,可以设置超时时间
参考《实战Java高并发程序设计》
————————————————
版权声明:本文为CSDN博主「蛇皮皮蛋」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_37598011/article/details/81952267

推荐阅读