qttcp通讯在子线程无法连接_多线程异步调用之Future模式

0ae6ec545b6989d29e355f508cf9e14b.png

一、什么是异步调用

当我们调用一个函数的时候,如果这个函数的执行过程是很耗时的,我们就必须要等待,但是我们有时候并不急着要这个函数返回的结果。因此,我们可以让被调者立即返回,让他在后台慢慢的处理这个请求。对于调用者来说,则可以先处理一些其他事情,在真正需要数据的时候再去尝试获得需要的数据(这个真正需要数据的位置也就是上文提到的阻塞点)。这也是Future模式的核心思想:异步调用。

到了这里,你可能会想CountDownLatch不是也可以实现类似的功能的吗?也是可以让耗时的任务通过子线程的方式去执行,然后设置一个阻塞点等待返回的结果,情况貌似是这样的!但有时发现CountDownLatch只知道子线程的完成情况是不够的,如果在子线程完成后获取其计算的结果,那CountDownLatch就有些捉襟见衬了,所以JDK提供的Future类,不仅可以在子线程完成后收集其结果,还可以设定子线程的超时时间,避免主任务一直等待。

看到这里,似乎恍然大悟了!CountDownLatch无法很好的洞察子线程执行的结果,使用Future就可以完成这一操作,那么Future何方神圣!下边我们就细细聊一下。

二、Future模式

虽然,Future模式不会立即返回你需要的数据,但是,他会返回一个契约 ,以后在使用到数据的时候就可以通过这个契约获取到需要的数据。

830e406ad9a098a15f0dd668bade8969.png

上图显示的是一个串行程序调用的流程,可以看出当有一个程序执行的时候比较耗时的时候,其他程序必须等待该耗时操作的结束,这样的话客户端就必须一直等待,知道返回数据才执行其他的任务处理。

0f8579054c5678a2a34b9067c0e05120.png

上图展示的是Future模式流程图,在广义的Future模式中,虽然获取数据是一个耗时的操作,但是服务程序不等数据完成就立即返回客户端一个伪造的数据(就是上述说的“契约”),实现了Future模式的客户端并不急于对其进行处理,而是先去处理其他业务,充分利用了等待的时间,这也是Future模式的核心所在,在完成了其他数据无关的任务之后,最后在使用返回比较慢的Future数据。这样在整个调用的过程中就不会出现长时间的等待,充分利用时间,从而提高系统效率。

1、Future主要角色

fa2db42d1bbc10b23a753d24beeebab1.png

2、Future的核心结构图如下:

308ba03cb5b6ea5b43dd82195bb8db8e.png

上述的流程就是说:Data为核心接口,这是客户端希望获取的数据,在Future模式中,这个Data接口有两个重要的实现,分别是:RealData和FutureData。RealData就是真实的数据,FutureData他是用来提取RealData真是数据的接口实现,用于立即返回得到的,他实际上是真实数据RealData的代理,封装了获取RealData的等待过程。

说了这些理论的东西,倒不如直接看代码来的直接些,请看代码!

三、Future模式的简单实现

主要包含以下5个类,对应着Future模式的主要角色:

6386746391692b68b38590b91084888c.png

1、Data接口

/** * 返回数据的接口 */public interface Data { String getResult();}

2、FutureData代码

/** * Future数据,构造很快,但是是一个虚拟的数据,需要装配RealData */public class FutureData implements Data { private RealData realData = null; private boolean isReady = false; private ReentrantLock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); @Override public String getResult() { while (!isReady) { try { lock.lock(); condition.await(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } return realData.getResult(); } public void setRealData(RealData realData) { lock.lock(); if (isReady) { return; } this.realData = realData; isReady = true; condition.signal(); lock.unlock(); }}

3、RealData代码

public class RealData implements Data { private String result; public RealData(String param) { StringBuffer sb = new StringBuffer(); sb.append(param); try { //模拟构造真实数据的耗时操作 Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } result = sb.toString(); } @Override public String getResult() { return result; }}

4、Client代码

public class Client { public Data request(String param) { //立即返回FutureData FutureData futureData = new FutureData(); //开启ClientThread线程装配RealData new Thread(() -> { { //装配RealData RealData realData = new RealData(param); futureData.setRealData(realData); } }).start(); return futureData; }}

5、Main

/** * 系统启动,调用Client发出请求 */public class Main { public static void main(String[] args) { Client client = new Client(); Data data = client.request("Hello Future!"); System.out.println("请求完毕!"); try { //模拟处理其他业务 Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("真实数据:" + data.getResult()); }}

6、执行结果:

6cf688bac0dbe938b704467913941e99.png

四、JDK中的Future模式实现

上述实现了一个简单的Future模式的实现,因为这是一个很常用的模式,在JDK中也给我们提供了对应的方法和接口,先看一下实例:

public class RealData implements Callable { private String result; public RealData(String result) { this.result = result; } @Override public String call() throws Exception { StringBuffer sb = new StringBuffer(); sb.append(result); //模拟耗时的构造数据过程 Thread.sleep(5000); return sb.toString(); }}

这里的RealData 实现了Callable接口,重写了call方法,在call方法里边实现了构造真实数据耗时的操作。

public class FutureMain { public static void main(String[] args) throws ExecutionException, InterruptedException { FutureTask futureTask = new FutureTask<>(new RealData("Hello")); ExecutorService executorService = Executors.newFixedThreadPool(1); executorService.execute(futureTask); System.out.println("请求完毕!"); try { Thread.sleep(2000); System.out.println("这里经过了一个2秒的操作!"); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("真实数据:" + futureTask.get()); executorService.shutdown(); }}

执行结果:

3573222b59fc8382915cf31caa9370fd.png

上述代码,通过:FutureTask futureTask = new FutureTask<>(new RealData("Hello")); 这一行构造了一个futureTask 对象,表示这个任务是有返回值的,返回类型为String,下边看一下FutureTask的类图关系:

5bcd342f009f918542e78eafd07dc1f3.png

FutureTask实现了RunnableFuture接口,RunnableFuture接口继承了Future和Runnable接口。因为RunnableFuture实现了Runnable接口,因此FutureTask可以提交给Executor进行执行,FutureTask有两个构造方法,如下:

构造方法1,参数为Callable:

40bb9b8cfc9a6bfb3edf176b7bd9d92b.png

构造方法2,参数为Runnable:

011893161a41a6a30164c48ff2c8288e.png

上述的第二个构造方法,传入的是Runnable接口的话,会通过Executors.callable()方法转化为Callable,适配过程如下:

46fcc2a33c2be6dd31940c9d26ccaabb.png
0c7a1a4aaa6243ce6306d30b31487dd4.png

这里为什么要将Runnable转化为Callable哪?首先看一下两者之间的区别:

(1) Callable规定的方法是call(),Runnable规定的方法是run();

(2) Callable的任务执行后可返回值,而Runnable的任务是不能返回值得;

(3) call()方法可以抛出异常,run()方法不可以;

(4) 运行Callable任务可以拿到一个Future对象,Future 表示异步计算的结果。

最关键的是第二点,就是Callable具有返回值,而Runnable没有返回值。Callable提供了检查计算是否完成的方法,以等待计算的完成,并获取计算的结果。

计算完成后只能使用 get 方法来获取结果,如果线程没有执行完,Future.get()方法可能会阻塞当前线程的执行;如果线程出现异常,Future.get()会throws InterruptedException或者ExecutionException;如果线程已经取消,会抛出CancellationException。取消由cancel 方法来执行。isDone确定任务是正常完成还是被取消了。

一旦计算完成,就不能再取消计算。如果为了可取消性而使用 Future 但又不提供可用的结果,则可以声明Future> 形式类型、并返回 null 作为底层任务的结果。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值