1.什么是Futrue模式?
Future模式是多线程开发中非常常见的一种设计模式。它的核心思想是异步调用。当我们需要调用一个函数方法时。如果这个函数执行很慢,那么我们就要进行等待。但有时候,我们可能并不急着要结果。因此,我们可以让被调用者立即返回,让他在后台慢慢处理这个请求。对于调用者来说,则可以先处理一些其他任务,在真正需要数据的场合再去尝试获取需要的数据。
用生活中的例子来打个比喻,就像叫外卖。比如在午休之前我们可以提前叫外卖,只需要点好食物,下个单。然后我们可以继续工作。到了中午下班的时候外卖也就到了,然后就可以吃个午餐,再美滋滋的睡个午觉。而如果你在下班的时候才叫外卖,那就只能坐在那里干等着外卖小哥,最后拿到外卖吃完午饭,午休时间也差不多结束了。
使用Future模式,获取数据的时候无法立即得到需要的数据。而是先拿到一个契约,你可以再将来需要的时候再用这个契约去获取需要的数据,这个契约就好比叫外卖的例子里的外卖订单。
2.怎么学习Future?
2.1 Future模式的简单实现
首先是FutureData,它只是一个包装类,创建它不需要耗时。在工作线程准备好数据之后可以使用setRealData方法将数据传入。而客户端线程只需要在需要的时候调用getRealData方法即可,如果这个时候数据还没有准备好,那么getData方法就会等待,如果已经准备好了就好直接返回。
public class FutrueData<T> {
//结果数据是否生成
private boolean isReady = false;
//结果数据
private T realData;
public synchronized void setRealData(T realData) {
//如果结果数据已经生成,直接返回
if(isReady) {
return;
}
this.realData = realData;
this.isReady = true;
notify();
}
public synchronized T getRealData() {
//如果结果数据没有生成,一直阻塞
while(!isReady) {
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//
return this.realData;
}
}
其次,MySupplier接口,提供了getResult()方法,封装了具体要获取的数据。
import java.util.function.Supplier;
public class MySupplier implements Supplier<String>{
private Object parameter;
public MySupplier(Object parameter) {
this.parameter = parameter;
}
@Override
public String get() {
//这里是真实的业务逻辑
System.out.println("根据" + parameter + "进行查询,这是一个很耗时的操作..");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("操作完毕,获取结果");
return "查询结果";
}
}
接着FutrueClient客户端去请求数据,不会实际去加载数据,它只是创建一个FutureData,然后创建子线程去加载,而它只需要直接返回FutureData对象。
import java.util.function.Supplier;
public class FutrueClient<T> {
public FutrueData<T> request(Supplier<T> supplier){
final FutrueData<T> futrueData = new FutrueData<>();
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
futrueData.setRealData(supplier.get());
}
}).start();
return futrueData;
}
}
Main主类,测试
public class Main {
public static void main(String[] args) {
// TODO Auto-generated method stub
FutrueClient<String> futrueClient = new FutrueClient<>();
System.out.println("开始时间:" + System.currentTimeMillis());
String parameter = "[参数]";
FutrueData<String> futrueData = futrueClient.request(new MySupplier(parameter));
System.out.println("请求发送成功!");
try {
System.out.println("做其他的事情...");
//在5秒内干其他事情,并行消耗3秒时间
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
String result = futrueData.getRealData();
System.out.println(result);
System.out.println("结束时间:" + System.currentTimeMillis());
}
}
2.2 JDK的内置实现
Java 1.5之后提供了Callable和Future接口
Callable接口
对于需要执行的任务需要实现Callable接口,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是个泛型接口,泛型V就是要call()方法返回的类型。Callable接口和Runnable接口很像,都可以被另外一个线程执行,但是正如前面所说的,Runnable不会返回数据也不能抛出异常。
Future接口
Future接口代表异步计算的结果,通过Future接口提供的方法可以查看异步计算是否执行完成,或者等待执行结果并获取执行结果,同时还可以取消执行。Future接口的定义如下:
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
具体使用如下:
MyCallable:
import java.util.concurrent.Callable;
public class MyCallable implements Callable<String> {
private Object parameter;
public MyCallable(Object parameter) {
this.parameter = parameter;
}
@Override
public String call() {
//这里是真实的业务逻辑
System.out.println("根据" + parameter + "进行查询,这是一个很耗时的操作..");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("操作完毕,获取结果");
return "查询结果";
}
}
Main:
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class Main {
public static void main(String[] args) throws Exception, ExecutionException {
// TODO Auto-generated method stub
System.out.println("开始时间:" + System.currentTimeMillis());
String parameter = "[参数]";
ExecutorService executor = Executors.newFixedThreadPool(1);
Future<String> futrue = executor.submit(new MyCallable(parameter));
System.out.println("请求发送成功!");
try {
System.out.println("做其他的事情...");
//在5秒内干其他事情,并行消耗3秒时间
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
String result = futrue.get();
System.out.println(result);
System.out.println("结束时间:" + System.currentTimeMillis());
executor.shutdown();
}
}
至此,Future的普通场景以及原理就结束了,futrue接口还有许多高级的使用场景和实现,本文档只是简单的阐述了通用的方式