【转】异步调用--Future模式:返回伪造数据

转自:https://blog.csdn.net/yanluandai1985/article/details/82459687

一、Future模式的基本思想
如果某个方法的执行过程非常耗时,并且我们又不着急要这个方法的返回结果。

假设在单线程情况下,我们就必须要等待。

这个时候,可以修改此耗时的方法,让其立即返回客户端一个伪造的数据

并且,在这个耗时的方法中开启另外一条线程,在后台慢慢计算

主线程继续执行

如果主线程需要用耗时方法的返回结果的时候,若子线程中返回结果没有计算完毕,那么主线程需要等待耗时方法计算完毕

因此,此刻的主线程的状态为阻塞状态,需要等待子线程完成计算。

所以,CountDownLatch这个类的缺点就很明显,如果子线程耗时过多,那么主线程也会一直等待,程序执行效率大大降低

下面的例子中说明了Future模式的基本思想:开启另外一个线程,在后台计算结果,主线程立刻返回一个伪造的结果,不影响主线程执行其它任务

 

 

 

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
* Created by jay.zhou on 2018/9/6.
*/
public class Client<T> {
public Data<T> request(T param) {
//立即构造一个 伪造的数据给主线程
FutureData<T> futureData = new FutureData<T>();

//同时开启一个子线程,去进行计算
Runnable calculateTask = () -> {
//构造真实的RealeData,这将会消耗一些时间
RealData<T> realData = new RealData<>(param);
//并且将计算结果装配到 客户端的伪造的FutureData中
futureData.setRealData(realData);
};
new Thread(calculateTask).start();
System.out.println("立刻返回伪造数据");
//立即返回一个 伪造的数据
return futureData;
}

//主函数测试,主函数其实就是主线程,可以理解为应用程序的主干线程
public static void main(String[] args) {
Client<String> client = new Client<>();
Data data = client.request("异步调用--Future模式");
System.out.println("主线程其它任务执行");
//使用伪造结果做事情,伪造数据需要等到真实数据装配完毕才能调用。否则阻塞主线程。
System.out.println(data.getResult());
}

}

interface Data<T> {
T getResult();
}

//返回给客户端的伪造的数据,内部拥有真实的计算出来的数据
class FutureData<T> implements Data<T> {
//伪造的数据需要 装配一个真实的数据
private RealData<T> realData;
//开启的线程是否已经执行完毕
private boolean isReady = false;
//getResult()与setRealData()方法应该是同步的,原因参考getResult()方法注释
private ReentrantLock lock = new ReentrantLock();
//在设置realData完毕之后,唤醒需要用到realData的getResult方法的线程
private Condition condition = lock.newCondition();

//如果主线程需要获取计算结果,那么返回的这个结果应该是 RealData 计算的结果
//如果RealData还没构造完毕,那么此方法应该阻塞,等待RealData构造完毕
//所以,通过realData获取结果的getResult方法应该与 设置realData的setRealData方法同步
//为了同步,此类应该增加同步锁
@Override
public T getResult() {
//先进入同步状态
lock.lock();
//本方法的目标就是获取realData的值,所以要排除获取不到的情况
while (!isReady) {
try {
condition.await();
} catch (InterruptedException e) {
lock.lock();
e.printStackTrace();
}
}
//如果装配好了,即isReady=true,释放同步锁
lock.unlock();
//返回执行结果
return realData.getResult();
}

public void setRealData(RealData<T> realData) {
//既然调用了setRealData方法,说明子线程已经构造好了realData对象
lock.lock();
//健壮性判断,不允许设置两次realData
if (this.realData != null) {
lock.unlock();
return;
}
this.realData = realData;
//装配成功
isReady = true;
//唤醒getResult方法中的需要此值的线程
condition.signal();
//释放同步锁
lock.unlock();

}
}

//真实的数据,构造函数模拟复杂的构造逻辑
class RealData<T> implements Data<T> {
private T param;

//真实返回结果的构造函数需要耗费一定的时间
public RealData(T param) {
this.param = param;
//模拟构造花费5秒时间
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

@Override
public T getResult() {
return param;
}
}

 

二、JDK对Future的支持

java.util.concurrent.FutureTask<V> 类实现了 Runnable接口,Future接口,RunnableFuture接口。

Future表示异步计算的结果。

FutureTask类在计算完成后,可以通过get()方法获取其结果。若构造函数中接收的任务尚未完成,调用get()方法,将会出现线程阻塞,直到任务完成。

FutureTask包装的是Callable或者Runnable,用的是构造函数接收的。因为FutureTask实现了Runnable接口,所以可以提交给ThreadPoolExecutor的execute(Runnable)执行。

 

 

 

首先,创建一个任务,要么实现Runnable接口,要么实现Callable接口。

再使用FutureTask进行封装。为什么构造函数接收Runnable的时候要加上值,因为Runnable()接口实现的run()方法没有返回值。而Callable接口实现的call()方法,是有返回值的,并且还能抛出异常。

因为FutureTask实现了Runnable接口,所以可以提交给ThreadPoolExecutor的execute(Runnable)执行。

FutureTask的get()方法,获取的是Callable()接口实现call()方法的返回值。若没有取消任务,且任务call()任务执行较为耗时没有执行完毕,那么get()方法将会一直阻塞,直到任务结束。

主线程中的ThreadPoolExecutor.execute(futureTask);将不会阻塞主线程。直到主线程需要调用FutureTask的get()方法获取返回值的时候,才可能出现主线程阻塞。

import java.util.concurrent.*;

/**
* Created by jay.zhou on 2018/9/6.
*/
public class FutureTaskDemo {

public static void main(String[] args) throws InterruptedException, ExecutionException {
Callable<String> task = () -> {
//模拟计算时间
Thread.sleep(5000);
return "Callable接口实现的返回结果";
};
//创建FutureTask来包装Callable接口实现
FutureTask<String> futureTask = new FutureTask<>(task);
//创建线程池准备执行任务
ExecutorService service = Executors.newFixedThreadPool(1);
//执行任务,线程池将会分配一个线程去执行指定的任务
service.execute(futureTask);
//主线程执行其它任务
Thread.sleep(2000);
System.out.println("主线程执行其它任务花费了2秒");
//主线程需要子线程任务的结果
String result = futureTask.get();
System.out.println("FutureTask任务的执行结果是:"+result);
//关闭线程池
service.shutdown();
}
}

三、Callable接口与Runnable接口的区别
(1)Callable接口的方法是call(),Runnable接口的方法是run()

(2)Callable的任务执行后可返回值,而Runnable的任务执行后没有返回值

(3)Callable的任务执行中可以抛异常,而Runnable的任务不能抛出异常
————————————————
版权声明:本文为CSDN博主「小大宇」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/yanluandai1985/article/details/82459687

posted @ 2022-04-14 14:47  飘飘雪  阅读(83)  评论(0编辑  收藏  举报