Scala和Java期货显然有意想不到的相互作用

Zim*_*oot 9 java concurrency multithreading scala scala-java-interop

我们在Scala Play Framework应用程序中使用Elasticsearch 0.90.7,其中"doSearch"方法的结尾如下所示:

def doSearch(...) = {
  ...
  val actionRequessBuilder: ActionRequestBuilder // constructed earlier in the method
  val executedFuture: ListenableActionFuture<Response> = actionRequestBuilder.execute
  return executedFuture.actionGet
}
Run Code Online (Sandbox Code Playgroud)

在哪里ListenableActionFuture延伸java.util.concurrent.Future,和ListenableActionFuture#actionGet基本相同Future#get

当我们按顺序执行搜索时,这一切都正常,但是当我们尝试并行执行多个搜索时:

val search1 = scala.concurrent.Future(doSearch(...))
val search2 = scala.concurrent.Future(doSearch(...))
return Await.result(search1, defaultDuration) -> Await.result(search2, defaultDuration))
Run Code Online (Sandbox Code Playgroud)

我们有时(不到1%或2%的时间)在我们的scala期货上获得意外超时,即使在qa期间使用极长的超时(5秒,搜索总是在不到200ms内执行).使用scala全局执行上下文以及使用Play默认执行上下文时也会发生这种情况.

由于Java未来包含在scala未来中,是否存在某种意外的交互?我原本以为actionGet在结束时对java未来的调用doSearch会阻止两个期货相互干扰,但显然可能并非如此.

som*_*ytt 2

我认为封锁是邪恶的,这是在某个地方确立的。邪恶的!

在这种情况下,Await.result将阻塞当前线程,因为它正在等待结果。

Await将调用包装在 中blocking,试图通知线程池它可能想要增加一些线程以维持其所需的并行性并避免死锁。

如果当前线程不是 Scala BlockContext,那么您只会遇到阻塞。

无论您的精确配置如何,大概您在阻塞时都保留着一个线程,并且您运行搜索的 thunk 想要运行某些内容但不能运行,因为池已耗尽。

相关的是哪个池产生了当前线程:如果在底层,您需要使用当前池中的更多线程并且它已耗尽,则中间的 Future 是否位于不同的池上并不重要。

当然,这只是一个猜测。

拥有一个从两次搜索中获取价值并带有超时的单一 future 更有意义。

但如果你最终拥有多个 Future,那么使用Future.sequence并等待它是有意义的。