同步代码和异步代码_告别异步代码

同步代码和异步代码

Quasar是一个将真正的轻量级线程(纤维)添加到JVM的库。 它们非常便宜且非常快-实际上,光纤的行为就像Erlang进程或Go goroutines-并允许您编写简单的阻塞代码,同时享受与复杂异步代码相同的性能优势。

在本文中,我们将学习如何将任何基于回调的异步API转换为漂亮的(光纤)阻塞API。 它适用于希望将自己的或第三方库与Quasar光纤集成的用户。 如果您仅将Quasar光纤与通道或演员一起使用,或者利用Comsat项目中已经提供的许多集成功能,则不需要了解这些知识(下面提供的代码是应用程序开发人员从未看到的代码)。 但是,即使您不这样做,您也可能会发现这篇文章对理解Quasar如何发挥其魔力很有帮助。

为什么异步?

首先,许多库提供异步API的原因是OS可以处理的正在运行的1个线程的数量远远少于OS可以维护的开放TCP连接的数量。 也就是说,您的机器可以支持比线程所提供的更高的并发性,因此库以及使用它们的开发人员会放弃线程作为用于软件并发2单元的抽象。 异步API不会阻塞线程,并且会导致显着的性能提升(通常在吞吐量和服务器容量方面,而在延迟方面却没有那么多)。

但是,使用异步API也会创建正确获得“回调地狱”名称的代码。 在缺乏多核处理的环境(例如Javascript)中,回调地狱已经很糟糕了。 在诸如JVM之类的地方,您需要关心内存可见性和同步性会变得更糟。

编写在光纤上运行的阻塞代码具有与异步代码相同的优点,但没有缺点:您使用了不错的阻塞API(甚至可以继续使用现有的API),但是却获得了非阻塞代码的所有性能优势。

可以肯定的是,异步API还有一个优势:它们使您可以同时分派多个IO操作(例如HTTP请求)。 因为这些操作通常需要很长时间才能完成,而且通常是独立的,所以我们可以同时等待其中的几个完成。 但是,Java期货也可以使用此有用的功能,而无需回调。 稍后,我们将看到如何制作博克期货。

光纤异步

许多现代的Java IO /数据库库/驱动程序都提供两种API:一种是同步(线程)阻塞的API,另一种是基于回调的异步API(对于NIO,JAX-RS客户端,Apache HTTP客户端以及更多的API来说都是如此。 )。 同步API更好。

Quasar有一个编程工具,可以将任何基于回调的异步API转换为一个很好的阻止光纤的API: FiberAsync 。 本质上, FiberASync作用是阻止当前光纤,安装异步回调,并在触发该回调时,它将再次唤醒光纤,并返回操作结果(如果失败,则引发异常)。

为了了解如何使用FiberAsync ,我们将看一个API示例: FooClientFooClient是一种现代的IO API,因此有两种形式,一种是同步的,线程阻塞的一种,另一种是异步的。 他们来了:

interface FooClient {String op(String arg) throws FooException, InterruptedException;
}interface AsyncFooClient {Future<String> asyncOp(String arg, FooCompletion<String> callback);
}interface FooCompletion<T> {void success(T result);void failure(FooException exception);
}

请注意异步操作(如许多现代库中的情况)如何都需要回调并返回前途。 现在,让我们忽略未来。 我们稍后再讲。

FooClientAsyncFooClient更好,更简单,但是它阻塞了线程并大大降低了吞吐量。 我们想要创建一个FooClient接口的实现,该接口可以在光纤中运行并阻塞光纤,因此我们获得了简单的代码出色的吞吐量。 为此,我们将在AsyncFooClient使用AsyncFooClient ,但将其转换为阻止光纤的FooClient 。 这是我们需要的所有代码(我们将进一步对其进行简化):

public class FiberFooClient implements FooClient {private final AsyncFooClient asyncClient;public FiberFooClient(AsyncFooClient asyncClient) {this.asyncClient = asyncClient;}@Override@SuspendableString op(final String arg) throws FooException, InterruptedException {try {return new FiberAsync<String, FooException>() {@Overrideprotected void requestAsync() {asyncClient.asyncOp(arg, new FooCompletion<String>() {public void success(String result) {FiberAsync.this.asyncCompleted(result);}public void failure(FooException exception) {FiberAsync.this.asyncFailed(exception);}});}}.run();} catch(SuspendExecution e) {throw new AssertionError(e);}}
}

现在,这是怎么回事? 我们正在实施的FooClient接口,但我们正在做op纤维粘连,而不是线程阻塞。 我们需要告诉Quasar我们的方法是光纤阻塞(或“可挂起”),因此我们使用@Suspendable对其进行@Suspendable

然后,我们将FiberAsync子类FiberAsync并实现requestAsync方法( FiberAsync接受的两个通用类型参数是返回类型和操作可能抛出的已检查异常的类型(如果有的话);对于未检查的异常,第二个通用参数应为RuntimeException )。 requestAsync负责启动异步操作并注册回调。 然后,回调需要调用asyncCompleted (如果操作成功)并将其传递给我们希望返回的结果,或者asyncFailed (如果操作失败)并将失败原因的异常传递给它。

最后,我们调用FiberAsync.run() 。 这将阻止当前光纤,并调用requestAsync以安装回调。 纤维将保持阻塞,直到回调被触发,它会释放出FiberAsync通过调用或者asyncCompletedasyncFailedrun方法还具有一个带超时参数的版本,如果我们想对阻塞操作进行时间限制(通常是个好主意),该方法很有用。

需要解释的另一件事是try/catch块。 有两种方法可声明为可@Suspendable的方法:用@Suspendable对其进行注释,或声明其引发已检查的异常SuspendExecutionFiberAsyncrun方法使用了后者,因此为了编译代码,我们需要捕获SuspendExecution ,但是由于它不是真正的异常,因此我们永远无法真正捕获它(嗯,至少在Quasar运行正常的情况下,至少不是这样) –因此为AssertionError

完成后,您可以在任何光纤中使用op ,如下所示:

new Fiber<Void>(() ->{// ...String res = client.op();// ...
}).start();

顺便说一句,所有的要短很多与脉冲星 (类星体的Clojure的API),其中异步操作:

(async-op arg #(println "result:" %))

使用Pulsar的await宏将其转换为以下同步的光纤阻塞代码:

(println "result:" (await (async-op arg)))

简化和批量生产

通常,像FooClient这样的接口将具有许多方法,并且通常, AsyncFooClient大多数方法将采用相同类型的回调( FooCompletion )。 如果是这种情况,我们可以将我们已经看到的许多代码封装到FiberAsync的命名子类中:

abstract class FooAsync<T> extends FiberAsync<T, FooException> implements FooCompletion<T> {@Overridepublic void success(T result) {asyncCompleted(result);}@Overridepublic void failure(FooException exception) {asyncFailed(exception);}@Override@Suspendablepublic T run() throws FooException, InterruptedException {try {return super.run();} catch (SuspendExecution e) {throw new AssertionError();}}@Override@Suspendablepublic T run(long timeout, TimeUnit unit) throws FooException, InterruptedException, TimeoutException {try {return super.run(timeout, unit);} catch (SuspendExecution e) {throw new AssertionError();}}
}

请注意,我们如何使FiberAsync直接实现FooCompletion回调–不是必需的,但这是一个有用的模式。 现在,我们的光纤阻塞op方法要简单得多,并且该接口中的其他操作也可以轻松实现:

@Override
@Suspendable
public String op(final String arg) throws FooException, InterruptedException {return new FooAsync<String>() {protected void requestAsync() {asyncClient.asyncOp(arg, this);}}.run();
}

有时,我们可能希望在常规线程而不是光纤上调用op方法。 默认情况下,如果在线程上调用FiberAsync.run()FiberAsync.run()引发异常。 为了解决这个问题,我们要做的就是实现另一个FiberAsync方法requestSync ,如果在光纤上调用run ,它将调用原始的同步API。 我们的最终代码如下(我们假设FiberFooClass具有类型为FooClientsyncClient字段):

@Override
@Suspendable
public String op(final String arg) throws FooException, InterruptedException {return new FooAsync<String>() {protected void requestAsync() {asyncClient.asyncOp(arg, this);}public String requestSync() {return syncClient.op(arg);}}.run();
}

就是这样!

期货

期货是一种方便的方式,可以在我们等待所有独立的IO操作完成时同时开始。 我们希望我们的纤维能够阻挡期货。 许多Java库通过其异步操作返回期货,因此用户可以在完全异步,基于回调的用法和采用期货的“半同步”用法之间进行选择。 我们的AsyncFooClient接口就是这样。

这是我们实现AsyncFooClient版本的AsyncFooClient ,该版本返回阻塞光纤的期货:

import co.paralleluniverse.strands.SettableFuture;public class FiberFooAsyncClient implements FooClient {private final AsyncFooClient asyncClient;public FiberFooClient(AsyncFooClient asyncClient) {this.asyncClient = asyncClient;}@Overridepublic Future<String> asyncOp(String arg, FooCompletion<String> callback) {final SettableFuture<T> future = new SettableFuture<>();asyncClient.asyncOp(arg, callbackFuture(future, callback))return future;}private static <T> FooCompletion<T> callbackFuture(final SettableFuture<T> future, final FooCompletion<T> callback) {return new FooCompletion<T>() {@Overridepublic void success(T result) {future.set(result);callback.completed(result);}@Overridepublic void failure(Exception ex) {future.setException(ex);callback.failed(ex);}@Overridepublic void cancelled() {future.cancel(true);callback.cancelled();}};}
}

如果返回, co.paralleluniverse.strands.SettableFuture返回co.paralleluniverse.strands.SettableFuture ,如果我们在光纤或普通线程(即任何类型的绞线上 )上对其进行阻塞,则效果同样良好。

JDK 8的CompletableFuture和Guava的ListenableFuture

可以使用预先构建的FiberAsync使返回CompletionStage (或实现它的CompletableFuture )的API(在JDK 8中添加到Java中)变得更容易实现光纤阻塞。 例如,

CompletableFuture<String> asyncOp(String arg);

通过以下方式变成光纤阻塞呼叫:

String res = AsyncCompletionStage.get(asyncOp(arg));

返回Google Guava的方法类似地转换为光纤阻塞同步,因此:

ListenableFuture<String> asyncOp(String arg);

通过以下方式变成光纤阻塞:

String res = AsyncListenableFuture.get(asyncOp(arg));

期货的替代品

尽管期货是有用且熟悉的,但我们实际上并不需要使用纤维时返回它们的特殊API。 产生的纤维是如此便宜( Fiber类实现了Future ,因此纤维本身可以代替“手工”的期货。 这是一个例子:

void work() {Fiber<String> f1 = new Fiber<>(() -> fiberFooClient.op("first operation"));Fiber<String> f2 = new Fiber<>(() -> fiberFooClient.op("second operation"));String res1 = f1.get();String res2 = f2.get();
}

因此,即使我们使用的API不提供,光纤也可以为我们提供期货。

如果没有异步API怎么办?

有时我们很不幸地遇到一个仅提供同步的线程阻塞API的库。 JDBC是此类API的主要示例。 尽管Quasar不能提高使用此类库的吞吐量,但仍然值得使API光纤兼容(实际上非​​常容易)。 为什么? 因为调用同步服务的光纤也可能做其他事情。 实际上,它们可能很少调用该服务(仅当发生高速缓存未命中时,才考虑从RDBMS读取数据的光纤)。

实现此目的的方法是通过在专用线程池中执行实际的调用,然后通过FiberAsync封装该假的异步API,将阻塞API转变为异步API。 这个过程是如此机械, FiberAsync有一些静态方法可以为我们处理所有事情。 因此,假设我们的服务仅公开了阻塞的FooClient API。 要使其成为光纤阻塞,我们要做的是:

public class SadFiberFooClient implements FooClient {private final FooClient client;private static final ExecutorService FOO_EXECUTOR = Executors.newCachedThreadPool();public FiberFooClient(FooClient client) {this.client = client;}@Override@SuspendableString op(final String arg) throws FooException, InterruptedException {try {return FiberAsync.runBlocking(FOO_EXECUTOR, () -> client.op());} catch(SuspendExecution e) {throw new AssertionError(e);}}
}

FooClient此实现可以安全地用于线程和光纤。 实际上,当在普通线程上调用该方法时,该方法将不会麻烦将操作分配给提供的线程池,而是在当前线程上执行该操作-就像我们使用原始FooClient实现时那样。

结论

此处显示的技术FiberAsynccpstrands.SettableFuture正是构成Comsat项目的集成模块的工作方式。 Comsat包括Servlet,JAX-RS(服务器和客户端),JDBC,JDBI,jOOQ,MongoDB,Retrofit和Dropwizard的集成。

重要的是要查看如何-创建简单且高性能的光纤阻塞API-我们确实重新实现了API 接口 ,但没有实现其内部工作:仍然仅通过其异步API来使用原始库代码,其丑陋之处在于现在对图书馆用户隐藏了。

额外信用:单子怎么样?

除了纤程外,还有其他方法可以处理回调地狱。 JVM世界中最著名的机制是Scala的可组合期货,RxJava的可观察对象以及JDK 8的CompletionStage / CompletableFuture 。 这些都是单子和单子组成的例子。 Monad可以工作,有些人喜欢使用它们,但是我认为对于大多数编程语言而言,它们是错误的方法。

您会看到,单子是从基于lambda演算的编程语言中借用的。 Lambda演算是一种理论计算模型,与Turing机器完全不同,但完全类似。 但是与图灵机模型不同,lambda演算计算没有步骤,动作或状态的概念。 这些计算没有任何事情; 他们只是。 那么,Monads是Haskell等基于LC的语言将动作,状态,时间等描述为纯计算的一种方式。 它们是LC语言告诉计算机“先执行然后再执行”的一种方法。

问题是,命令式语言已经有了“先做然后再做”的抽象,而这种抽象就是线程。 不仅如此,而且是必须的语言通常有一个非常简单的符号“这样做,然后做”:声明后跟语句。 命令性语言甚至考虑采用这种外来概念的唯一原因是因为(通过OS内核)线程的实现不令人满意。 但是,与其采用一个陌生,陌生的概念(并且该概念需要完全不同的API类型),不如采用一个相似但细微不同的抽象,最好是修复(线程)的实现。 光纤保留抽象并修复实现。

Java和Scala等语言中的monad的另一个问题是,这些语言不仅势在必行,而且还允许不受限制的共享状态突变和副作用-Haskell却没有。 无限制的共享状态突变和“线程”单核的结合可能是灾难性的。 在纯FP语言中-由于副作用是受控的-计算单位(即函数)也是并发单位:您可以安全地同时执行任何一对函数。 当您不受限制的副作用时,情况并非如此。 函数执行的顺序,两个函数是否可以同时执行以及一个函数是否以及何时可以观察到另一个函数执行的共享状态突变都是非常重要的问题。 结果,作为“线程” monad的一部分运行的函数要么必须是纯函数(没有任何副作用),要么必须非常小心如何执行这些副作用。 这正是我们要避免的事情。 因此,尽管单子组合确实比回调地狱生成了更好的代码,但它们不能解决异步代码引入的任何并发问题。

聚苯乙烯

上一节不应理解为像Haskell这样的纯“ FP”语言的认可,因为我实际上认为它们带来了太多其他问题。 我相信(不久的将来)命令性语言3将允许共享状态变异,但具有一些事务语义。 我相信那些未来的语言将主要从Clojure和Erlang等语言中获得灵感。

  1. 通过运行我的意思是线程往往不够可运行↩
  2. 见利特尔法则,可扩展性和容错 ↩
  3. 它们是否“功能性”是一个难题,因为没有人对功能性编程语言是什么以及它与非功能性语言的区别提出了很好的定义。 ↩

翻译自: https://www.javacodegeeks.com/2015/04/farewell-to-asynchronous-code.html

同步代码和异步代码

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/337897.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java socket 传送进度_java-★-Socket文件上传/进度条

客户端代码&#xff1a;1、客户端运行程序&#xff1a;package wtb.khd;import java.awt.event.ActionEvent;import java.awt.event.ActionListener;import java.io.DataOutputStream;import java.io.File;import java.io.FileInputStream;import java.io.OutputStream;import …

c语言中typedef和define的区别

点击上方蓝字关注我&#xff0c;了解更多咨询1、typedef仅限于为类型定义符号名称。define不仅可以为类型定义别名&#xff0c;还可以为数值定义别名。例如&#xff0c;可以将1定义为ONE。2、typedef由编译器解释&#xff0c;define语句由预编译器处理。实例#include <stdio…

c语言中预处理器是什么?

点击上方蓝字关注我&#xff0c;了解更多咨询1、C语言有预处理器&#xff0c;Java中没有这个概念&#xff0c;其实只是文本替换工具。2、C的预处理器&#xff0c;即CPP&#xff0c;将在实际编译器中完成处理&#xff0c;所有预处理命令将从#开始。实例#include <stdio.h>…

java缓存同步_浅谈JSON的数据交换、缓存问题和同步问题

JSON轻量级的数据交换格式相对于XML来说&#xff0c;JSON的解析速度更快&#xff0c;文档更小。JSON的格式{属性名:属性值,属性名:属性值,……}属性名的类型可以是string,number,boolean,null,object,且属性名必须用双引号引起来&#xff0c;如果属性值是字符串&#xff0c;也必…

垃圾回收算法以及垃圾回收器_什么是垃圾回收?

垃圾回收算法以及垃圾回收器以下是我们的垃圾收集手册中的一个示例&#xff0c;该手册将在接下来的几周内发布。 同时&#xff0c;花点时间熟悉垃圾收集的基础知识-这将是本书的第一章。 乍一看&#xff0c;垃圾收集应该处理顾名思义的问题–查找并丢弃垃圾。 实际上&#xff…

c语言中fgetc函数的介绍

点击上方蓝字关注我&#xff0c;了解更多咨询1、fgetc函数返回的字符实际上是文件流中位置指针指向的字符。当fgetc函数读取错误时&#xff0c;返回EOF并设置文件错误标志位。2、该函数以无符号char强制转换为int的形式返回读取的字符&#xff0c;如果到达文件末尾或出现读错&a…

java中什么表示打印_在java中打印对象时会发生什么

您不需要调试器就能知道发生了什么.System.out是PrintStream类型.PrintStream.println(Object)的javadoc说&#xff1a;Prints an Object and then terminate the line. This method calls at first String.valueOf(x) to get the printed object’s string value, then behave…

魔术笔反选_魔术二传手反图案

魔术笔反选设置者和获取者是邪恶的。 创建JavaBean定义时&#xff0c;这似乎是个好主意。 但是它们对Java社区造成了很大的伤害。 通常不如null指针那么多&#xff0c;但足够了。 首先&#xff0c;许多初级人员相信实现setter和getter&#xff08;嘿&#xff0c;在Eclispe中只…

花5分钟了解C语言基本语法元素快来

点击上方蓝字关注我&#xff0c;了解更多咨询C语言是一种编程语言&#xff0c;和其它语言一样&#xff0c;也定义了自己的语法和词汇。学习C语言&#xff0c;首先要学习C语言的词汇&#xff0c;再学习C语言的语法规则&#xff0c;然后由词汇构成语句&#xff0c;由语句构成源程…

java8 策略模式_Java 8中的策略模式

java8 策略模式这是两个有关如何使用Java 8功能样式以及Cyclops模式匹配和Hamcrest库来实现策略模式设计的示例。 PrintDependingOnInput方法是一种策略&#xff0c;该策略将根据传递的日志对System.println一些消息。 AddPrefix是另一种策略&#xff0c;它将根据邮件内容向邮…

c语言中ftell函数是什么

点击上方蓝字关注我&#xff0c;了解更多咨询1、C语言函数ftell用于获取文件位置指针当前位置相对于文件首的偏移字节数。2、通过ftell函数获取当前文件的大小&#xff0c;然后通过fread函数读取缓冲区。返回值&#xff0c;如果成功&#xff0c;该函数返回位置标识符的当前值&a…

jdk入门_JDK 9 REPL:入门

jdk入门会议是聚会Java名人的好地方。 Devoxx France是与Java语言架构师&#xff0c;前同事和老朋友Brian Goetz&#xff08; briangoetz &#xff09;见面的一个机会。 我们谈论了JDK 9&#xff0c;而他全都热衷于REPL。 他提到&#xff0c;尽管Java SE 9中有很多重要功能 &am…

c语言中局部变量是什么

点击上方蓝字关注我&#xff0c;了解更多咨询1、函数内部定义的变量称为局部变量&#xff0c;其作用域仅限于函数内部&#xff0c;离开函数后无效&#xff0c;使用后报错。2、局部变量只能在函数内部使用&#xff0c;离开函数后无效&#xff0c;再次使用会报错。实例#include &…

java接口自动化Excel占位符_基于maven+java+TestNG+httpclient+poi+jsonpath+ExtentReport的接口自动化测试框架...

接口自动化框架项目说明本框架是一套基于mavenjavaTestNGhttpclientpoijsonpathExtentReport而设计的数据驱动接口自动化测试框架&#xff0c;TestNG 作为执行器&#xff0c;poi用于读取存放于excel的接口用例&#xff0c;jsonPath用于校验返回值&#xff0c;以及提取返回值。本…

本地构建和自动化构建_构建自动化面板

本地构建和自动化构建上周二&#xff0c;我作为持续讨论&#xff08;&#xff03;c9d9&#xff09;的一部分&#xff0c;参加了一个关于Build Automation主题的在线讨论会&#xff0c;这是一系列有关敏捷&#xff0c;持续交付和DevOps的社区讨论会。 自动化构建流程面临许多挑战…

C语言为什么要定义short,int,long这么多整数类型?

点击上方蓝字关注我&#xff0c;了解更多咨询整数类型有int、short int、long int三种类型&#xff0c;用于需要不同存储空间的整数使用。整数类型有正整数和负整数之分&#xff0c;在C语言中&#xff0c;规定整型的最高位为符号位&#xff0c;最高位为“0”表示正数&#xff0…

java中逗号怎么加_Java中如何将字符串从右至左每三位加一逗号

/*** * 将字符串从右至左每三位加一逗号* ** param str 需要加逗号的字符串* return 以从右至左每隔3位加一逗号显示*/public static String displayWithComma(String str){str new StringBuffer(str).reverse().toString(); // 先将字符串颠倒顺序String str2 "";…

小度拆卸_拆卸invokedynamic

小度拆卸许多Java开发人员认为JDK的第七版有些令人失望。 从表面上看&#xff0c;仅少数语言和库扩展使它成为了发行版&#xff0c;即Project Coin和NIO2 。 但在幕后&#xff0c;该平台的第七个版本对JVM类型系统进行了最大的扩展&#xff0c;这是其最初发行后引入的。 添加in…

c语言中函数参数类型的探究

点击上方蓝字关注我&#xff0c;了解更多咨询函数中的参数个数可以是0&#xff0c;也可以是一个或多个参数。下面我们带着这三种不同的情况&#xff0c;分别在c语言中进行讨论。1、函数没有参数&#xff0c;表示没有参数列表。int func1(); //声明一个函数&#xff0c;该函数…

java rotate怎么用_jQuery旋转插件jqueryrotate用法详解

本文实例讲述了jQuery旋转插件jqueryrotate用法。分享给大家供大家参考&#xff0c;具体如下&#xff1a;CSS3 提供了多种变形效果&#xff0c;比如矩阵变形、位移、缩放、旋转和倾斜等等&#xff0c;让页面更加生动活泼有趣&#xff0c;不再一动不动。然后 IE10 以下版本的浏览…