Executor and Execution Context Objects in Scala
Learn more about Executor and Execution Context objects in Scala.
Join the DZone community and get the full member experience.
Join For FreeThread and Runnable have been around for a long time as two of the first concurrent execution approaches in Scala. Creating a new Thread takes less computational time compared to creating a new JVM process. We cannot afford to create a fresh Thread for each of these tasks, because if an application performs a large number of small concurrent tasks, then it requires high throughput.
You may also like: A Scalable Java Thread Pool Executor
In this post, we are going to discuss the Executor and Execution Context objects with specific code examples. Let's get started.
Thread Pools
Starting a Thread required us to allocate a memory region for its call stack and context switch from one Thread to another. But this consumes much more time than work in the concurrent task. For this reason, most concurrency frameworks have facilities that maintain a set of Threads in a waiting state and start running when concurrently executable work tasks become available. Generally, we call such facilities Thread pools.
To allow programmers to encapsulate the decision of how to run concurrently executable work tasks, the JDK comes with an abstraction called Executor. The Executor is an interface, and in this, a single execute method is defined. This method takes a runnable object and calls its run method.
ForkJoinPool
ForkJoinPool
is an Executor introduced in JDK 7. Its threads are daemons by default, which means there is no need to shut it down explicitly at the end of the program. Scala programmers can use it in JDK 6 by importing scala.concurrent.forkjoin
package. Let’s see the implementation and submit tasks that can be asynchronously executed.
In this code, first, we import the package and instantiate ForkJoinPool
class and assign it to the executor. Now, this executor sent the task in the form of a runnable object that prints to the standard output. Finally, we use Thread.sleep to prevent the daemon threads in the ForkJoinPool
instance from being terminated.
ExecutorService
It is the subtype of Executor
interface which also implemented by the ForkJoinPool
class. ExecutorService
extends Executor
which defines convenience methods. In this, I will talk about the shutdown method, this method makes sure that the Executor
object terminates by all executing all the submitted tasks and then stopping all the worker threads. When your program no longer needs the ExecutorService
object you created, you should ensure that the shutdown method is called.
In the above example, we used the sleep
method. To prevent this, we can use the awaitTermination
method. This method specifies the maximum amount of time to wait for their completion. Let’s see the example (this code is a continuation of the above code).
import java.util.concurrent.TimeUnit
executor.shutdown()
executor.awaitTermination(60, TimeUnit.SECONDS)
Execution Context
If you are a Scala programmer, then you know that the scala.concurrent package defines the ExecutionContext
trait and offers similar functionality to that of the Executor
objects. Many Scala methods take ExecutionContext
objects as implicit parameters. It implements the abstract execute method, which perfectly corresponds to the execute
method on the Executor
interface and the reportFailure
method. The ExecutorContext
companion object contains the default execution called global, which internally uses a ForkJoinPool
instance. With this, we can pass the parameter or import the package.
Let’s look at an example:
object ExecutionContextGlobal extends App {
val ectx = ExecutionContext.global
ectx.execute(new Runnable {
def run() = log("Running on the execution context.")
})
Thread.sleep(500)
}
In this example, we instance the global to ectx and then send the task in the form of a Runnable object.
By importing the package:
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
val fut = Future { Thread.sleep(10000); 21 + 21 }
In this example, we imported global to globally, which means we don’t need to instance global again and again.
Thank you for reading to the end. If you liked this post, please do show your appreciation by giving it a like and sharing this blog. And don't forget to share your feedback in the comments.
Further Reading
Opinions expressed by DZone contributors are their own.
Comments