☰
java interrupt 高性能编程中,Future模式竟成香饽饽?快来看并发编程的秘密
2026/10/9 19:28:16 网站建设 项目流程

前言

就高性能编程这方面而言, 并发编程呢已经切实成为了极其重要的一部分, 在单核CPU性能已然趋向于极限的这种状况下, 我们仅仅能够借助多核去进一步提高系统的性能, 所以呢也就由此催生出了并发编程。

鉴于并发编程相较于串行编程而言, 难度更高, 出错几率更大, 所以, 我们更应当去借鉴一些前人所拥有的、成熟程度较高的设计模式, 以此让我们的设计变得更为健壮, 更为完美。

可是存在这样一种模式, 它在众多模式里, 是那种运用范围最为宽广的, 并且还是极其关键重要的一种设计模式。就在今天,要跟着阿浪去知晓一番这种所提及的模式!

生活中的模式

为了更快地了解模式,我们先来看一个生活中的例子。

场景1:

午饭时间来临, 同学们得去用餐了, 小王下楼梯, 行走了20分钟, 抵达了肯德基, 点取餐品, 排成队伍, 进餐总共耗费20分钟, 又花费20分钟返回公司接着工作, 总计1小时。

场景2

到午饭时间, 同学们都要去用餐, 小王去点了份肯德基的外卖, 没多久, 他就收到了一个订单, (虽说订单并不能当作饭来吃, 然而有了订单, 难道还害怕吃不上饭不成)。随后小王能够接着干活, 30分钟过后, 外卖送达了, 然后小王花了 10分钟去吃饭, 之后又能够接着工作, 顺利地卷过了隔壁的小汪。

明显看得出, 于这两个场景里, 小王的工作时间更为紧凑, 尤其是那些排队光阴都能够让外卖员去做, 所以能够更专注于自身本职工作。聪慧的你理应也已体会到, 场景一是典型的函数同步调用, 而场景二则是典型的异步调用。

场景2存在异步调用, 该异步调用有个特点, 即有一个具返回值, 此返回值为我们的订单, 此订单相当重要, 依靠此订单, 我们才可获取当前调用对应的结果。

这里的订单跟模式中之情形相似, 这是一种合约, 是一份承诺。虽说订单无法食用, 然而手握订单, 就不用担心没吃的, 虽说这并非我们期望的结果, 可拿着它就能在未来获得我们想要的结果。

因此,模式很好地解决了那些需要返回值的异步调用。

模式中的主要角色

一个典型的模式由以下几个部分组成:

它们之间的相互关系如下图:

其中, 值得留意的是Data, 以及。这属于一组典型的代理模式, Data接口用以表示对外数据, 代表真实的数据, 恰似午餐, 获取它的成本较高, 耗时长;相对而言作为代理, 类似一个订单/契约, 借助此, 能够在未来获取。

因此,模式本质上是代理模式的一种实际应用。

实现一个简单的模式

根据上面的设计,让我们来实现一个简单的代理模式吧!

首先是Data接口,代表数据:

public interface Data { public String getResult (); }

接着是,也是整个模式的核心:

public class FutureData implements Data { // 内部需要维护RealData protected RealData realdata = null; protected boolean isReady = false; public synchronized void setRealData(RealData realdata) { if (isReady) { return; } this.realdata = realdata; isReady = true; //RealData已经被注入,通知getResult() notifyAll(); } //会等待RealData构造完成 public synchronized String getResult() { while (!isReady) { try { //一直等待,直到RealData被注入 wait(); } catch (InterruptedException e) { } } //真正需要的数据从RealData获取 return realdata.result; } }

下面是:

public class RealData implements Data { protected final String result; public RealData(String para) { StringBuffer sb=new StringBuffer(); //假设这里很慢很慢,构造RealData不是一个容易的事 result =sb.toString(); } public String getResult() { return result; } }

然后从得到Data:

public class Client { //这是一个异步方法,返回的Data接口是一个Future public Data request(final String queryStr) { final FutureData future = new FutureData(); new Thread() { public void run() { // RealData的构建很慢,所以在单独的线程中进行 RealData realdata = new RealData(queryStr); //setRealData()的时候会notify()等待在这个future上的对象 future.setRealData(realdata); } }.start(); // FutureData会被立即返回,不会等待RealData被构造完 return future; } }

最后一个Main函数,把所有一切都串起来:

public static void main(String[] args) { Client client = new Client(); //这里会立即返回,因为得到的是FutureData而不是RealData Data data = client.request("name"); System.out.println("请求完毕"); try { //这里可以用一个sleep代替了对其他业务逻辑的处理 //在处理这些业务逻辑的过程中,RealData被创建,从而充分利用了等待时间 Thread.sleep(2000); } catch (InterruptedException e) { } //使用真实的数据,如果到这里数据还没有准备好,getResult()会等待数据准备完,再返回 System.out.println("数据 = " + data.getResult()); }

这是一种得以实现的模式里最为简单的那种, 虽说它简单, 然而却已然涵盖了模式含有的最为精粹至关重要的部分, 这对于大家去解析 JDK 内部的对象, 具备着相当关键突出不能被忽视的作用。

Java中的模式

模式这般常用, 于JDK内部已然存有较为全面的实现以及支持。接下来, 一块儿瞧瞧JDK内部的实现:

首先, 在JDK内部存在着一个接口, 此接口如同前面提及的订单那般, 当然啦, 鉴于其作为一个完备的商业化产品, 此处的功能更为丰富多样, 除去通过get()方法来获取真实数据之外, 还给出了一组辅助方法, 举例而言:

下面代码演示了这个的使用方法:

//异步操作 可以用一个线程池 ExecutorService executor = Executors.newFixedThreadPool(1); //执行FutureTask,相当于上例中的 client.request("name") 发送请求 //在这里开启线程进行RealData的call()执行 Future future = executor.submit(new RealData("name")); System.out.println("请求完毕,数据准备中"); try { //这里依然可以做额外的数据操作,这里使用sleep代替其他业务逻辑的处理 Thread.sleep(2000); } catch (InterruptedException e) { } //如果此时call()方法没有执行完成,则依然会等待 System.out.println("数据 = " + future.get());

全部运用进程特别简便, 接下来我们剖析一番()之内到底产生了啥:

public Future submit(Callable task) { if (task == null) throw new NullPointerException(); // 根据Callable对象,创建一个RunnableFuture,这里其实就是FutureTask RunnableFuture ftask = newTaskFor(task); //将ftask推送到线程池 //在新线程中执行的,就是run()方法,在下面的代码中有给出 execute(ftask); //返回这个Future,将来通过这个Future就可以得到执行的结果 return ftask; } protected RunnableFuture newTaskFor(Callable callable) { return new FutureTask(callable); }

最为关键的那部分处在下面, 当以一个线程单独去执行的时候, 会把结果给保存到里面, 并且会设置一下任务的状态, 下面呈现的是其run()方法:

从中获得结果的实现如下:

public V get() throws InterruptedException, ExecutionException { int s = state; //如果没有完成,就等待,回到用park()方法阻塞线程 //同时,所有等待线程会在FutureTask的waiters字段中排队等待 if (s <= COMPLETING) s = awaitDone(false, 0L); return report(s); } private V report(int s) throws ExecutionException { //outcome里保存的就是最终的计算结果 Object x = outcome; if (s == NORMAL) //正常完成,就返回outcome return (V)x; //如果没有正常完成, 比如被用户取消了,或者有异常了,就抛出异常 if (s >= CANCELLED) throw new CancellationException(); throw new ExecutionException((Throwable)x); }

模式的高阶版本——

模式好用归好用, 可存在一个问题, 就是把任务提交给线程后, 调用线程不清楚该任务何时执行完毕, 要是执行时调用get()方法或者()方法去判断, 或许会进行没必要的等待, 如此一来系统的吞吐量就很难提升了。

针对解决该问题, JDK对模式予以了进一步强化, 进而创建出一个, 其是能被视作模式的经过升级的版本, 最大作用在于提供了一种回调机制, 在任务完结之后, 能够自动去回调一些后续的处理, 如此一来, 整个程序能够将“结果等待”彻底予以移除。

下面来看一个简单的例子:

在这个事例当中, 最先凭借()去构建一个异步调用, 紧跟着, 运用()办法, 设定了一项后续的行动, 也便是当()运行完毕之后的后续处置。

能轻易发觉, 相较于一般的而言, 它具备更强的实用性, 这是由于它于执行成功之际, 能够实施自动回调以助推展开后续工作, 以至于整个流程里不会存在致使任何阻塞的情形(即意味着诸位无需在各处等候其开展工作, 而是在其执行成功以后, 它会自行向诸位进行反馈告知)。

以上面所提及的代码当作例子, 之所以会拥有那般神奇的功能, 这完全是借助于类, 而这里的类是由上述代码里的()方法创建而成的。

在执行时,如下所示:

public void run() { CompletableFuture d; Supplier f; if ((d = dep) != null && (f = fn) != null) { dep = null; fn = null; if (d.result == null) { try { //这里就是你要执行的异步方法 //结果会被保存下来,放到d.result字段中 d.completeValue(f.get()); } catch (Throwable ex) { d.completeThrowable(ex); } } //执行成功了,进行后续处理,在这个后续处理中,就会调用thenAccept()中的消费者 //这里就相当于Future完成后的通知 d.postComplete(); } }

继续看d.(),这里会调用后续一系列操作

final void postComplete() { //省略部分代码,重点在tryFire()里 //在tryFire()里,真正触发了后续的调用,也就是thenAccept()中的部分 f = (d = h.tryFire(NESTED)) == null ? this : d; } } }

絮叨

今天, 我们着重讲解模式, 我们起始于一个最为简单的模式, 循序渐进地深入, 依次介绍了JDK内部的模式达成情况, 并且对模式的进阶版本进行了扼要的介绍。就多线程开发来讲, 模式的运用极为广泛, 能够讲这个模式已然变为了异步开发的基础架构。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询