简体   繁体   English

为什么这个Scala代码在一个线程中执行两个Futures?

[英]Why this Scala code execute two Futures in one thread?

I've been using multiple threads for a long time, yet can not explain such a simple case. 我一直在使用多线程,但无法解释这么简单的情况。

import java.util.concurrent.Executors
import scala.concurrent._
implicit val ec = ExecutionContext.fromExecutor(Executors.newFixedThreadPool(1))

def addOne(x: Int) = Future(x + 1)
def addTwo(x: Int) = Future {addOne(x + 1)}

addTwo(1)
// res5: Future[Future[Int]] = Future(Success(Future(Success(3))))

To my surprise, it works. 令我惊讶的是,它有效。 And I don't know why. 我不知道为什么。

Question: 题:
Why given one thread can it execute two Futures at the same time? 为什么给定一个线程可以同时执行两个期货?

My expectation : 我的期望
The first Future ( addTwo ) is occupying the one and only thread ( newFixedThreadPool(1) ), then it calls another Future ( addOne ) which again needs another thread. 第一个FutureaddTwo )占用唯一的线程( newFixedThreadPool(1) ),然后调用另一个FutureaddOne ),它再次需要另一个线程。
So the program should end up starved for threads and get stuck. 所以该程序应该最终缺乏线程并陷入困境。

The reason that your code is working, is that both futures will be executed by the same thread. 你的代码工作的原因是两个期货都将由同一个线程执行。 The ExecutionContext that you are creating will not use a Thread directly for each Future but will instead schedule tasks ( Runnable instances) to be executed. 您正在创建的ExecutionContext将不会直接为每个Future使用Thread ,而是安排要执行的任务( Runnable实例)。 In case no more threads are available in the pool these tasks will be put into a BlockingQueue waiting to be executed. 如果池中没有可用的线程,这些任务将被放入等待执行的BlockingQueue (See ThreadPoolExecutor API for details) (有关详细信息,请参阅ThreadPoolExecutor API

If you look at the implementation of Executors.newFixedThreadPool(1) you'll see that creates an Executor with an unbounded queue: 如果查看Executors.newFixedThreadPool(1)的实现,您将看到创建一个具有无界队列的Executor:

new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue[Runnable])

To get the effect of thread-starvation that you were looking for, you could create an executor with a limited queue yourself: 要获得您正在寻找的线程饥饿的效果,您可以自己创建一个具有有限队列的执行程序:

 implicit val ec = ExecutionContext.fromExecutor(new ThreadPoolExecutor(1, 1, 0L, 
                     TimeUnit.MILLISECONDS, new ArrayBlockingQueue[Runnable](1)))

Since the minimal capacity of ArrayBlockingQueue is 1 you would need three futures to reach the limit, and you would also need to add some code to be executed on the result of the future, to keep them from completing (in the example below I do this by adding .map(identity) ) 由于ArrayBlockingQueue的最小容量为1,您需要三个期货才能达到限制,并且您还需要添加一些代码以便在未来的结果上执行,以防止它们完成(在下面的示例中我执行此操作)通过添加.map(identity)

The following example 以下示例

import scala.concurrent._
implicit val ec = ExecutionContext.fromExecutor(new ThreadPoolExecutor(1, 1, 0L, 
                      TimeUnit.MILLISECONDS, new ArrayBlockingQueue[Runnable](1)))

def addOne(x: Int) = Future {
  x + 1
}
def addTwo(x: Int) = Future {
  addOne(x + 1) .map(identity)
}
def addThree(x: Int) = Future {
  addTwo(x + 1).map(identity)
}

println(addThree(1))

fails with 失败了

java.util.concurrent.RejectedExecutionException: Task scala.concurrent.impl.CallbackRunnable@65a264b6 rejected from java.util.concurrent.ThreadPoolExecutor@10d078f4[Running, pool size = 1, active threads = 1, queued tasks = 1, completed tasks = 1]

expand it to Promise is easily to undunderstand 将它扩展到Promise很容易理解

val p1 = Promise[Future[Int]]
ec.execute(() => {
  // the fist task is start run
  val p2 = Promise[Int]
  //the second task is submit , but no run
  ec.execute(() => {
    p2.complete(Success(1))
    println(s"task 2 -> p1:${p1},p2:${p2}")
  })
  //here the p1 is completed, not wait p2.future finish
  p1.complete(Success(p2.future))
  println(s"task 1 -> p1:${p1},p2:${p2}")// you can see the p1 is completed but the p2 have not
  //first task is finish, will run second task
})
val result: Future[Future[Int]] = p1.future

Thread.sleep(1000)
println(result)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM