The differences between a Scala Future and a Java Thread

Someone asked an interesting question this past week: What’s the difference between a Scala Future and a Thread?

At first I thought the answer was pretty obvious, but as I tried to explain it, I realized that I needed to consider the differences more before speaking. After some thinking, I finally decided that the differences are in their APIs, and as a result of that, significant differences in how they are used.

If you’re just interested in my summary/conclusions, jump down to the Summary section. Otherwise, read on.

Warning: This article is a work-in-progress. I need to put much more research into it.

Scala/Java Thread API

Technically there is no such thing as a “Scala Thread.” If you type this in your Scala IDE:

val t = new Thread

you’ll see that a Thread is really a java.lang.Thread. Putting that technicality behind us, some of the most important parts of the Java Thread API looks like this:

Constructors:

    Thread()
    Thread(Runnable target)

Methods:

    interrupt
    join
    run
    start
    yield

Here’s an example of how you might use a Thread in Scala:

val thread = new Thread {
    override def run {
        // put your long-running code here ...
    }
}
thread.start

As the API and code shows, a Thread is a general-purpose concurrency abstract. While you may not notice it at this point, it’s important to note that the Thread has no return type.

The Thread Javadoc gives this description of a Thread:

A thread is a thread of execution in a program. The Java Virtual Machine allows an application to have multiple threads of execution running concurrently.

Scala Future

The Scala Future is well-described on the official Futures and Promises page:

Futures provide a nice way to reason about performing many operations in parallel -- in an efficient and non-blocking way. The idea is simple; a Future is a sort of a placeholder object that you can create for a result that does not yet exist. Generally, the result of the Future is computed concurrently and can be later collected. Composing concurrent tasks in this way tends to result in faster, asynchronous, non-blocking parallel code.

I read somewhere else that a Future “represents the result of an asynchronous computation,” and I like that description.

They also add this:

A Future is an object holding a value which may become available at some point. (My emphasis.)

This value is usually the result of some other computation: (1) If the computation has not yet completed, we say that the Future is not completed. (2) If the computation has completed with a value or with an exception, we say that the Future is completed.

The Future API is significantly different from a Java Thread. I provide a Future example here that shows you can create a Future like this:

val f = Future {
    // your long-running task here that returns an Int ...
}

Note that a Future has a type, so I could have written that code like this to be more explicit:

val f: Future[Int] = Future {
    // your long-running task here that returns an Int ...
}

A common way to use a Future is to use its callback methods. You can do this in a variety of ways. One way is with the onComplete method, where you handle whether the Future returned successfully, or not:

f.onComplete {
    case Success(value) => println(s"Got the callback, meaning = $value")
    case Failure(e) => e.printStackTrace
}

You also also use the onSuccess and onFailure callback methods, like this:

f onSuccess {
    case 0 => println("got a zero")
}

and this:

f onFailure {
    case t => println("D'oh! Got an error: " + t.getMessage)
}

In some cases it can be inconvenient to use these methods, so the Scala Future “provides combinators which allow a more straightforward composition.” The “Futures and Promises” page shows this example of how to use a map method with a Future:

val rateQuote = future {
    connection.getCurrentValue(USD)
}

val purchase = rateQuote map { quote => 
    if (isProfitable(quote)) connection.buy(amount, quote)
    else throw new Exception("not profitable")
}

purchase onSuccess {
    case _ => println("Purchased " + amount + " USD")
}

I’m not going to go into the details of this example, I just want to show another way the API is different. Please see the Futures and Promises tutorial to read more about this example.

To summarize the Scala Future, its API looks like this:

Creating a Future:

    val f = Future {
        // your long-running code here ...
    }

Some of the methods available on a Future:

    isCompleted
    onComplete
    ready
    result
    value
    
    andThen
    collect
    filter
    flatMap
    foreach
    map
    onFailure
    onSuccess
    recover
    recoverWith
    zip

Finally, I think this text from the “Futures and Promises” page is also important:

We should now comment on when exactly the callback gets called. Since it requires the value in the future to be available, it can only be called after the future is completed. However, there is no guarantee it will be called by the thread that completed the future or the thread which created the callback. Instead, the callback is executed by some thread, at some time after the future object is completed. We say that the callback is executed eventually.

So, when you manually use a Thread, you wait for that one thread to return, but when you use a Future, there’s no guarantee that the callback will be called on the same thread that your Future ran on.

The Future as a ConcurrentTask

As I wrote in this article, when you first start working with a Future, it may help to think of it as a “concurrent task.” I demonstrated that in this code:

package futures

import scala.concurrent.{Future => ConcurrentTask}           // rename
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{Failure, Success}

object FutureAsConcurrentTask extends App {

    // run some long-running task (task has type Future[Int] in this example)
    val task = ConcurrentTask {
        Cloud.executeLongRunningTask
    }

    // whenever the task completes, execute this code
    task.onComplete {
        case Success(value) => println(s"Got the callback, value = $value")
        case Failure(e) => println(s"D'oh! The task failed: ${e.getMessage}")
    }
}

In that code I renamed the Future class to ConcurrentTask when I imported it. At the time I wrote that article, that way of thinking helped me understand Scala futures.

Summary: Thread vs Future

I know that I need to write more about this and clarify my points, but as a summary for today:

  • A Java Thread represents a thread of execution in an Java/Scala/JVM application.
  • You can run code in parallel in a Thread, but a Thread does not have a return type.
  • A Scala Future represents the result of an asynchronous computation, and has a return type.
  • A Scala Future works with callback methods.
  • A Scala Future can be composed, and has usual collection methods like map, flatMap, filter, etc.
  • There is no guarantee that your Future’s callback method will be called on the same thread the future was run on.

Notes to self

As a “note to self,” I need to research and discuss how a thread works with the JVM’s shared memory model, and how the Scala Future compares to that.