【转】异步调用--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