Outdated Notice
When you want to write parallel and concurrent applications in Scala, you could still use the native Java Thread
— but the Scala Future makes parallel/concurrent programming much simpler, and it’s preferred.
Here’s a description of Future
from its Scaladoc:
“A Future represents a value which may or may not currently be available, but will be available at some point, or an exception if that value could not be made available.”
Thinking in futures
To help demonstrate this, in single-threaded programming you bind the result of a function call to a variable like this:
def aShortRunningTask(): Int = 42
val x = aShortRunningTask
With code like that, the value 42
is bound to the variable x
immediately.
When you’re working with a Future
, the assignment process looks similar:
def aLongRunningTask(): Future[Int] = ???
val x = aLongRunningTask
But because aLongRunningTask
takes an indeterminate amount of time to return, the value in x
may or may not be currently available, but it will be available at some point (in the future).
Another important point to know about futures is that they’re intended as a one-shot, “Handle this relatively slow computation on some other thread, and call me back with a result when you’re done” construct. (As a point of comparison, Akka actors are intended to run for a long time and respond to many requests during their lifetime, but each future you create is intended to be run only once.)
In this lesson you’ll see how to use futures, including how to run multiple futures in parallel and combine their results in a for-expression, along with other methods that are used to handle the value in a future once it returns.
Tip: If you’re just starting to work with futures and find the name
Future
to be confusing in the following examples, replace it with the nameConcurrentResult
, which might be easier to understand initially.
Source code
You can find the source code for this lesson at this URL:
An example in the REPL
A Scala Future
is used to create a temporary pocket of concurrency that you use for one-shot needs. You typically use it when you need to call an algorithm that runs an indeterminate amount of time — such as calling a web service or executing a long-running algorithm — so you therefore want to run it off of the main thread.
To demonstrate how this works, let’s start with an example of a Future
in the Scala REPL. First, paste in these import
statements:
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{Failure, Success}
Now, you’re ready to create a future. For example, here’s a future that sleeps for ten seconds and then returns the value 42
:
scala> val a = Future { Thread.sleep(10*1000); 42 }
a: scala.concurrent.Future[Int] = Future(<not completed>)
While that’s a simple example, it shows the basic approach: Just construct a new Future
with your long-running algorithm.
Because a Future
has a map
function, you use it as usual:
scala> val b = a.map(_ * 2)
b: scala.concurrent.Future[Int] = Future(<not completed>)
Initially this shows Future(<not completed>)
, but if you check b
’s value you’ll see that it eventually contains the expected result of 84
:
scala> b
res1: scala.concurrent.Future[Int] = Future(Success(84))
Notice that the 84
you expected is wrapped in a Success
, which is further wrapped in a Future
. This is a key point to know: The value in a Future
is always an instance of one of the Try
types: Success
or Failure
. Therefore, when working with the result of a future, use the usual Try
-handling techniques, or one of the other Future
callback methods.
One commonly used callback method is onComplete
, which takes a partial function in which you should handle the Success
and Failure
cases, like this:
a.onComplete {
case Success(value) => println(s"Got the callback, value = $value")
case Failure(e) => e.printStackTrace
}
When you paste that code in the REPL you’ll see the result:
Got the callback, value = 42
There are other ways to process the results from futures, and the most common methods are listed later in this lesson.
An example application
The following application (App
) provides an introduction to using multiple futures. It shows several key points about how to work with futures:
- How to create futures
- How to combine multiple futures in a
for
expression to obtain a single result - How to work with that result once you have it
A potentially slow-running method
First, imagine you have a method that accesses a web service to get the current price of a stock. Because it’s a web service it can be slow to return, and even fail. As a result, you create a method to run as a Future
. It takes a stock symbol as an input parameter and returns the stock price as a Double
inside a Future
, so its signature looks like this:
def getStockPrice(stockSymbol: String): Future[Double] = ???
To keep this tutorial simple we won’t access a real web service, so we’ll mock up a method that has a random run time before returning a result:
def getStockPrice(stockSymbol: String): Future[Double] = Future {
val r = scala.util.Random
val randomSleepTime = r.nextInt(3000)
val randomPrice = r.nextDouble * 1000
sleep(randomSleepTime)
randomPrice
}
That method sleeps a random time up to 3000 ms, and also returns a random stock price. Notice how simple it is to create a method that runs as a Future
: Just pass a block of code into the Future
constructor to create the method body.
Next, imagine that you’re instructed to get three stock prices in parallel, and return their results once all three return. To do so, you write code like this:
package futures
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import scala.util.{Failure, Success}
object MultipleFutures extends App {
// use this to determine the “delta time” below
val startTime = currentTime
// (a) create three futures
val aaplFuture = getStockPrice("AAPL")
val amznFuture = getStockPrice("AMZN")
val googFuture = getStockPrice("GOOG")
// (b) get a combined result in a for-expression
val result: Future[(Double, Double, Double)] = for {
aapl <- aaplFuture
amzn <- amznFuture
goog <- googFuture
} yield (aapl, amzn, goog)
// (c) do whatever you need to do with the results
result.onComplete {
case Success(x) => {
val totalTime = deltaTime(startTime)
println(s"In Success case, time delta: ${totalTime}")
println(s"The stock prices are: $x")
}
case Failure(e) => e.printStackTrace
}
// important for a short parallel demo: you need to keep
// the jvm’s main thread alive
sleep(5000)
def sleep(time: Long): Unit = Thread.sleep(time)
// a simulated web service
def getStockPrice(stockSymbol: String): Future[Double] = Future {
val r = scala.util.Random
val randomSleepTime = r.nextInt(3000)
println(s"For $stockSymbol, sleep time is $randomSleepTime")
val randomPrice = r.nextDouble * 1000
sleep(randomSleepTime)
randomPrice
}
def currentTime = System.currentTimeMillis()
def deltaTime(t0: Long) = currentTime - t0
}
Question: If everything truly runs in parallel, can you guess what the maximum value of the totalTime
will be?
Answer: Because the three simulated web service calls do run in parallel, the total time should never be much longer than three seconds (3000ms). If they were run in series, the algorithm might run up to nine seconds.
This can be a fun little application to experiment with, so you’re encouraged to clone the Github project and run it before continuing this lesson. When you do so, first run it to make sure it works as expected, then change it as desired. If you run into problems, add println
statements to the code so you can completely understand how it works.
Tip: The Github repository for this lesson also contains a class named
MultipleFuturesWithDebugOutput
that contains the same code with a lot of debugprintln
statements.
Creating the futures
Let’s walk through that code to see how it works. First, we create three futures with these lines of code:
val aaplFuture = getStockPrice("AAPL")
val amznFuture = getStockPrice("AMZN")
val googFuture = getStockPrice("GOOG")
As you saw, getStockPrice
is defined like this:
def getStockPrice(stockSymbol: String): Future[Double] = Future { ...
If you remember the lesson on companion objects, the way the body of that method works is that the code in between the curly braces is passed into the apply
method of Future
’s companion object, so the compiler translates that code to something like this:
def getStockPrice ... = Future.apply { method body here }
-----
An important thing to know about Future
is that it immediately begins running the block of code inside the curly braces — it isn’t like the Java Thread
, where you create an instance and later call its start
method. You can see this very clearly in the debug output of the MultipleFuturesWithDebugOutput
example, where the debug output in getStockPrice
prints three times when the AAPL, AMZN, and GOOG futures are created, almost immediately after the application is started.
The three method calls eventually return the simulated stock prices. In fact, people often use the word eventually with futures because you typically use them when the return time of the algorithm is indeterminate: You don’t know when you’ll get a result back, you just hope to get a successful result back “eventually” (though you may also get an unsuccessful result).
The for
expression
The for
expression in the application looks like this:
val result: Future[(Double, Double, Double)] = for {
aapl <- aaplFuture
amzn <- amznFuture
goog <- googFuture
} yield (aapl, amzn, goog)
You can read this as, “Whenever aapl
, amzn
, and goog
all return with their values, combine them in a tuple, and assign that value to the variable result
.” As shown, result
has the type Future[(Double, Double, Double)]
, which is a tuple that contains three Double
values, wrapped in a Future
container.
It’s important to know that the application’s main thread doesn’t stop when getStockPrice
is called, and it doesn’t stop at this for-expression either. In fact, if you print the result from System.currentTimeMillis()
before and after the for-expression, you probably won’t see a difference of more than a few milliseconds. You can see that for yourself in the MultipleFuturesWithDebugOutput
example.
onComplete
The final part of the application looks like this:
result.onComplete {
case Success(x) => {
val totalTime = deltaTime(startTime)
println(s"In Success case, time delta: ${totalTime}")
println(s"The stock prices are: $x")
}
case Failure(e) => e.printStackTrace
}
onComplete
is a method that’s available on a Future
, and you use it to process the future’s result as a side effect. In the same way that the foreach
method on collections classes returns Unit
and is only used for side effects, onComplete
returns Unit
and you only use it for side effects like printing the results, updating a GUI, updating a database, etc.
You can read that code as, “Whenever result
has a final value — i.e., after all of the futures return in the for-expression — come here. If everything returned successfully, run the println
statement shown in the Success
case. Otherwise, if an exception was thrown, go to the Failure
case and print the exception’s stack trace.”
As that code implies, it’s completely possible that a Future
may fail. For example, imagine that you call a web service, but the web service is down. That Future
instance will contain an exception, so when you call result.onComplete
like this, control will flow to the Failure
case.
It’s important to note that just as the JVM’s main thread didn’t stop at the for-expression, it doesn’t block here, either. The code inside onComplete
doesn’t execute until after the for-expression assigns a value to result
.
About that sleep
call
A final point to note about small examples like this is that you need to have a sleep
call at the end of your App
:
sleep(5000)
That call keeps the main thread of the JVM alive for five seconds. If you don’t include a call like this, the JVM’s main thread will exit before you get a result from the three futures, which are running on other threads. This isn’t usually a problem in the real world, but it’s needed for little demos like this.
The other code
There are a few println
statements in the code that use these methods:
def currentTime = System.currentTimeMillis()
def deltaTime(t0: Long) = System.currentTimeMillis() - t0
There are only a few println
statements in this code, so you can focus on how the main parts of the application works. However, as you’ll see in the Github code, there are many more println
statements in the MultipleFuturesWithDebugOutput
example so you can see exactly how futures work.
Other Future methods
Futures have other methods that you can use. Common callback methods are:
onComplete
onSuccess
onFailure
In addition to those methods, futures have methods that you’ll find on Scala collections classes, including:
filter
foreach
map
Other useful and well-named methods include:
andThen
fallbackTo
recoverWith
These methods and many more details are discussed on the “Futures and Promises” page.
Key points
While this was a short introduction, hopefully those examples give you an idea of how Scala futures work. A few key points about futures are:
- You construct futures to run tasks off of the main thread
- Futures are intended for one-shot, potentially long-running concurrent tasks that eventually return a value
- A future starts running as soon as you construct it
- A benefit of futures over threads is that they come with a variety of callback methods that simplify the process of working with concurrent threads, including the handling of exceptions and thread management
- Handle the result of a future with methods like
onComplete
, or combinator methods likemap
,flatMap
,filter
,andThen
, etc. - The value in a
Future
is always an instance of one of theTry
types:Success
orFailure
- If you’re using multiple futures to yield a single result, you’ll often want to combine them in a for-expression
See also
- A small demo GUI application named Future Board was written to accompany this lesson. It works a little like Flipboard, updating a group of news sources simultaneously. You can find the source code for Future Board in this Github repository.
- While futures are intended for one-short, relatively short-lived concurrent processes, Akka is an “actor model” library for Scala, and provides a terrific way to implement long-running parallel processes. (If this term is new to you, an actor is a long-running process that runs in parallel to the main application thread, and responds to messages that are sent to it.)