dask futures_并发性:Java Futures和Kotlin Coroutines

本文探讨了Java中的Future API和Kotlin中的Coroutines在处理并发任务时的差异,强调了代码可读性的提升。通过比较两者创建可调用对象集合和处理这些调用的方式,展示了Kotlin Coroutines如何简化并发编程,提供了更简洁的代码实现。
摘要由CSDN通过智能技术生成

dask futures

很久以前,想要在Java中同时运行代码时,必须手动启动新线程。 这不仅很难编写,而且很容易引入难以发现的错误。 测试,阅读和维护此类代码也不是一件容易的事。 从那时起-在多核机器的推动下,Java API不断发展,使并发代码的开发更加容易。 同时,其他JVM语言也对帮助开发人员编写此类代码有自己的见解。 在本文中,我将比较它在Java和Kotlin中的实现方式。

为了使文章重点突出,我特意省略了性能,以撰写有关代码可读性的文章。

关于用例

用例不是很原始。 我们需要调用不同的Web服务。 天真的解决方案是依次调用它们,一个接一个,然后收集每个结果。 在那种情况下,总的呼叫时间将是每个服务的呼叫时间的总和。 一个简单的改进是并行调用它们,并等待最后一个完成。 因此,性能从线性提高到常数-或更精确地说,从o(n)提高o(1)

为了模拟延迟地调用Web服务,让我们使用以下代码(在Kotlin中,因为它的详细程度要低得多):

classDummyService(privatevalname:String){

    privatevalrandom=SecureRandom()

    valcontent:ContentDuration
        get(){
            valduration=random.nextInt(5000)
            Thread.sleep(duration.toLong())
            returnContentDuration(name,duration)
        }
}

dataclassContentDuration(valcontent:String,valduration:Int)

Java Future API

Java提供了一个完整的类层次结构来处理并发调用。 它基于以下类别:

可召回

Callable是“返回结果的任务”。 从另一个角度来看,它类似于不带任何参数并返回此结果的函数。

未来

Future是“异步计算的结果”。 另外,“只能在计算完成时使用get方法检索结果,必要时将阻塞直到准备就绪为止”。 换句话说,它表示一个值的包装,其中该值是计算的结果。

执行人服务

ExecutorService “提供了用于管理终止的方法以及可以生成用于跟踪一个或多个异步任务进度的Future方法”。 它是Java中并发处理代码的入口点。 可以通过Executors类中的静态方法来获得此接口的实现以及更为专门的接口。

下面的类图中对此进行了总结:

Java util并发

使用并发包致电我们的服务需要两个步骤。

创建可调用对象的集合

首先,需要有一组Callable才能传递给执行程序服务。 这可能是这样的:

  1. 来自服务名称流
  2. 对于每个服务名称,创建一个新的虚拟服务,该服务以字符串初始化
  3. 对于每个服务, Callable返回服务的getContent() 方法引用 之所以Callable是因为方法签名与Callable.call()相匹配,并且Callable是一个功能接口。

这是准备阶段。 它将转换为以下代码:

List<Callable<ContentDuration>>callables=Stream.of("Service A","Service B","Service C")
      .map(DummyService::new)
      .map(service->(Callable<ContentDuration>)service::getContent)
      .collect(Collectors.toList());

处理可调用项

清单准备好之后,就该由ExecutorService来处理它了, 就是“实际工作”。

  1. 创建一个新的执行者服务-任何都可以
  2. Callable的列表传递给执行者服务,并流式传输Future的结果列表
  3. 对于每个未来,
  4. 要么返回结果
  5. 或处理异常

以下代码段是可能的实现:

ExecutorServiceexecutor=Executors.newWorkStealingPool();
List<ContentDuration>results=executor.invokeAll(callables).stream()
    .map(future->{
         try{returnfuture.get();}
         catch(InterruptedException|ExecutionExceptione){thrownewRuntimeException(e);}
     }).collect(Collectors.toList());

Future API,但在Kotlin中

让我们面对现实吧,尽管Java使得编写并发代码成为可能,但是读取和维护它并不是那么容易,主要是因为:

  • 在集合和流之间来回
  • 在Lambda中处理检查的异常
  • 显式投射

只需将上面的代码移植到Kotlin即可消除这些限制并使之更加简单:

varcallables:List<Callable<ContentDuration>>=arrayOf("Service A","Service B","Service C")
    .map{DummyService(it)}
    .map{Callable<ContentDuration>{it.content}}

valexecutor=Executors.newWorkStealingPool()
valresults=executor.invokeAll(callables).map{it.get()}

Kotlin协程

随着Kotlin 1.1版的推出,新的实验功能称为协程

基本上,协程是可以在不阻塞线程的情况下挂起的计算。 阻塞线程通常很昂贵,尤其是在高负载下。 另一方面,协程悬架几乎是免费的。 不需要上下文切换或操作系统的任何其他参与。

协程背后的主要设计原则是,它们必须像顺序代码一样运行,而像并发代码一样运行。 它们基于以下(简化的)类图:

协程

但是,没有什么比代码本身更好。 让我们实现与上面相同的方法,但是在Kotlin中使用协程而不是Java期货。

作为第一步,让我们扩展服务,以通过添加围绕类型为Deferred content的新计算属性来简化进一步处理:

valDummyService.asyncContent:Deferred<ContentDuration>
    get()=async(CommonPool){content}

这是标准的Kotlin扩展属性代码,但请注意CommonPool参数。 这就是使代码并发运行的魔力。 它是一个伴随对象( 单例),它使用多重后备算法来获取ExecutorService实例。

现在,继续正确的代码流程:

  1. 协程在一个块内处理。 在要在其中分配的块外声明一个变量列表
  2. 打开同步块
  3. 创建服务名称数组
  4. 为每个名称创建一个服务并返回
  5. 对于每个服务,获取其异步内容(如上所述)并返回
  6. 对于每个延迟的结果,将其返回并返回
// Variable must be initialized or the compiler complains
// And the variable cannot be used afterwards
varresults=runBlocking{
    arrayOf("Service A","Service B","Service C")
        .map{DummyService(it)}
        .map{it.asyncContent}
        .map{it.await()}
}

外卖

除了Java语言本身之外,Future API并不是什么大问题。 一旦将代码翻译成Kotlin,可读性就会大大提高。 但是,必须创建一个集合以传递给执行程序服务,这会破坏功能管道。

在协程方面,请记住,它们仍处于实验阶段。 尽管如此,代码的确看起来是顺序的-因此更具可读性,并且表现出并行性。

可以在Github上以Maven格式找到此帖子的完整源代码。

翻译自: https://blog.frankel.ch/concurrency-java-futures-kotlin-coroutines/

dask futures

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值