11

This answer instructs how to convert java.util.concurrent.Future into scala.concurrent.Future, while managing where the blocking will occur:

import java.util.concurrent.{Future => JFuture}
import scala.concurrent.{Future => SFuture}

val jfuture: JFuture[T] = ???
val promise = Promise[T]()
new Thread(
  new Runnable {
    def run() { promise.complete(Try{ jfuture.get }) }
  }
).start
val future = promise.future

My queston is the same as a question asked in the comments:

what's wrong with future { jfuture.get }? Why you used an extra thread combined with Promise?

It was answered as follows:

it'll block thread in your thread pull. If you have a configured ExecutionContext for such futures it's fine, but default ExecutionContext contains as many threads as you have processors.

I'm not sure I understand the explanation. To reiterate:

What's wrong with future { jfuture.get }? Isn't blocking inside a future the same as manually creating a new Thread and blocking there? If not, how is it different?

Community
  • 1
  • 1
Dominykas Mostauskis
  • 7,797
  • 3
  • 48
  • 67
  • what exactly do you not understand? What does blocking of a thread means? – Alexei Kaigorodov Jan 17 '14 at 16:45
  • @AlexeiKaigorodov I have somewhat revised my question: What's wrong with `future { jfuture.get }`? Isn't blocking inside a future the same as manually creating a new Thread and blocking there? If not, how is it different? – Dominykas Mostauskis Jan 17 '14 at 16:55
  • Yes blocking inside a future is AS BAD as manually creating a new Thread and blocking there. The idea of converting to `scala.concurrent.Future` is to avoid blocking entirely by using `onComplete` instead of `get`. – Alexei Kaigorodov Jan 17 '14 at 18:24

2 Answers2

8

There is almost no difference between future { jfuture.get } and future { future { jfuture.get }}.

There are as many treads in default thread pool as many you have processors.

With jfuture.get you'll get 1 thread blocked.

Let's assume you have 8 processors. Also let's suppose each jfuture.get takes 10 seconds. Now create 8 future { jfuture.get }.

val format = new java.text.SimpleDateFormat("HH:mm:ss").format(_: Date)

val startTime = new Date
(1 to 8) map {_ => future{ Thread.sleep(10000) }}
future{
  2+2
  println(s"2+2 done. Start time: ${format(startTime)}, end time: ${format(new Date)}")
}

// 2+2 done. Start time: 20:48:18, end time: 20:48:28

10 seconds is a little too long for 2+2 evaluation.

All other futures and all actors on the same execution context will be stopped for 10 seconds.

With additional execution context:

object BlockingExecution {
  val executor = ExecutionContext.fromExecutor(new ForkJoinPool(20))
}

def blockingFuture[T](f: => T) = {
  future( f )(BlockingExecution.executor)
}

val startTime = new Date
(1 to 8) map {_ => blockingFuture{ Thread.sleep(10000) }}
future{
  2+2
  println(s"2+2 done. Start time: ${format(startTime)}, end time: ${format(new Date)}")
}

// 2+2 done. Start time: 21:26:18, end time: 21:26:18

You could implement blockingFuture using new Thread(new Runnable {..., but additional execution context allows you to limit threads count.

senia
  • 37,745
  • 4
  • 88
  • 129
  • There's no inherent reason that the future that computes 2+2 needs to be executed after the other futures. (You have simply asked for 9 things to be computed asynchronously.) If you change `Future { Thread.sleep(10000) }` to `Future { blocking { Thread.sleep(10000) } }`, the 2+2 future will execute immediately. My understanding is that the `blocking` method gives a hint so that the thread pool moves on to process other futures first. I haven't found any good resources describing exactly what is going on here. – Patrick Aug 22 '14 at 21:30
  • @Patrick that is true. when using `blocking` the global thread pool will spawn a new thread for that computation so that it doesn't run out of threads. More info here: http://docs.scala-lang.org/overviews/core/futures.html See the "Blocking" section – simao Jul 01 '15 at 07:41
7

It's actually quite simple. scala.concurrent.Promise is a concrete implementation of a Future, destined to be an asynchronous computation.

When you want to convert, with jfuture.get, you are running a blocking computation and outputting an immediately resolved scala.concurrent.Future.

The Thread will block until the computation inside jfuture is complete. The get method is blocking.

Blocking means nothing else will happen inside that Thread until the computation is complete. You are essentially monopolising the Thread with something that looks like a while loop intermittently checking for results.

while (!isDone() && !timeout) {
   // check if the computation is complete
}

Specifically:

val jfuture: JFuture[T] = ??? // some blocking task

When blocking cannot be avoided, the common practice is to spawn a new Thread and a new Runnable or new Callable to allow the computation to execute/monopolize a child thread.

In the example @senia gave:

new Thread(new Runnable { def run() {
  promise.complete(Try{ jfuture.get })
}}).start

How is this different than future {jfuture.get}? It doesn't block your default ExecutionContext, provided by Scala, which has as many threads as the processors of the machine.

That would mean all other futures in your code will always have to wait for future { jfuture.get } to complete, since the entire context is blocked.

flavian
  • 28,161
  • 11
  • 65
  • 105
  • I am under the impression that spawning a new thread and executing `somethingThatBlocks()` in it is the same as future(somethingThatBlocks()). My question is mostly about whether that is true, and if not, how is it different? – Dominykas Mostauskis Jan 17 '14 at 17:01
  • so in general it is not recommended to have lengthy computations inside a future with default execution context? – Denis Tulskiy Jan 17 '14 at 17:01
  • 1
    @DominykasMostauskis: no, default execution context has a thread pool and chooses one of them to run the code inside of the future. It does not span new thread every time because creating a thread is an expensive operation and threads are a limited resource. – Denis Tulskiy Jan 17 '14 at 17:04