Book Image

Learning Concurrent Programming in Scala - Second Edition

By : Aleksandar Prokopec
Book Image

Learning Concurrent Programming in Scala - Second Edition

By: Aleksandar Prokopec

Overview of this book

Scala is a modern, multiparadigm programming language designed to express common programming patterns in a concise, elegant, and type-safe way. Scala smoothly integrates the features of object-oriented and functional languages. In this second edition, you will find updated coverage of the Scala 2.12 platform. The Scala 2.12 series targets Java 8 and requires it for execution. The book starts by introducing you to the foundations of concurrent programming on the JVM, outlining the basics of the Java Memory Model, and then shows some of the classic building blocks of concurrency, such as the atomic variables, thread pools, and concurrent data structures, along with the caveats of traditional concurrency. The book then walks you through different high-level concurrency abstractions, each tailored toward a specific class of programming tasks, while touching on the latest advancements of async programming capabilities of Scala. It also covers some useful patterns and idioms to use with the techniques described. Finally, the book presents an overview of when to use which concurrency library and demonstrates how they all work together, and then presents new exciting approaches to building concurrent and distributed systems. Who this book is written for If you are a Scala programmer with no prior knowledge of concurrent programming, or seeking to broaden your existing knowledge about concurrency, this book is for you. Basic knowledge of the Scala programming language will be helpful.
Table of Contents (19 chapters)
Learning Concurrent Programming in Scala - Second Edition
Credits
Foreword
About the Author
Acknowledgements
About the Reviewers
www.PacktPub.com
Customer Feedback
Preface

Preliminaries


This book assumes a basic familiarity with sequential programming. While we advise readers to get acquainted with the Scala programming language, an understanding of a similar language, such as Java or C#, should be sufficient for this book. A basic familiarity with concepts in object-oriented programming, such as classes, objects, and interfaces, is helpful. Similarly, a basic understanding of functional programming principles, such as first-class functions, purity, and type-polymorphism are beneficial in understanding this book but are not a strict prerequisite.

Execution of a Scala program

To better understand the execution model of Scala programs, let's consider a simple program that uses the square method to compute the square value of the number 5, and then prints the result to the standard output:

object SquareOf5 extends App { 
  def square(x: Int): Int = x * x 
  val s = square(5) 
  println(s"Result: $s") 
} 

We can run this program using the Simple Build Tool (SBT), as described in the Preface. When a Scala program runs, the JVM runtime allocates the memory required for the program. Here, we consider two important memory regions--the call stack and the object heap. The call stack is a region of memory in which the program stores information about the local variables and parameters of the currently executed methods. The object heap is a region of memory in which objects are allocated by the program. To understand the difference between the two regions, we consider a simplified scenario of this program's execution.

First, in figure 1, the program allocates an entry to the call stack for the local variable s. Then, it calls the square method in figure 2 to compute the value for the local variable s. The program places the value 5 on the call stack, which serves as the value for the x parameter. It also reserves a stack entry for the return value of the method. At this point, the program can execute the square method, so it multiplies the x parameter by itself, and places the return value 25 on the stack in figure 3. This is shown in the first row in the following illustration:

After the square method returns the result, the result 25 is copied into the stack entry for the local variable s, as shown in figure 4. Now, the program must create the string for the println statement. In Scala, strings are represented as object instances of the String class, so the program allocates a new String object to the object heap, as illustrated in figure 5. Finally, in figure 6, the program stores the reference to the newly allocated object into the stack entry x, and calls the println method.

Although this demonstration is greatly simplified, it shows the basic execution model for Scala programs. In Chapter 2, Concurrency on the JVM and the Java Memory Model, we will learn that each thread of execution maintains a separate call stack, and that threads mainly communicate by modifying the object heap. We will learn that the disparity between the state of the heap and the local call stack is frequently responsible for certain kinds of error in concurrent programs.

Having seen an example of how Scala programs are typically executed, we now proceed to an overview of Scala features that are essential to understand the contents of this book.

A Scala primer

In this section, we present a short overview of the Scala programming language features that are used in the examples in this book. This is a quick and cursory glance through the basics of Scala. Note that this section is not meant to be a complete introduction to Scala. This is to remind you about some of the language's features, and contrast them with similar languages that might be familiar to you. If you would like to learn more about Scala, refer to some of the books referred to in the Summary of this chapter.

A Printer class, which takes a greeting parameter and has two methods named printMessage and printNumber, is declared as follows:

class Printer(val greeting: String) { 
  def printMessage(): Unit = println(greeting + "!") 
  def printNumber(x: Int): Unit = { 
    println("Number: " + x) 
  } 
}

In the preceding code, the printMessage method does not take any arguments and contains a single println statement. The printNumber method takes a single argument x of the Int type. Neither method returns a value, which is denoted by the Unit type.

We instantiate the class and call its methods as follows:

val printy = new Printer("Hi") 
printy.printMessage() 
printy.printNumber(5) 

Scala allows the declaration of singleton objects. This is like declaring a class and instantiating its single instance at the same time. We saw the SquareOf5 singleton object earlier, which was used to declare a simple Scala program. The following singleton object, named Test, declares a single Pi field and initializes it with the value 3.14:

object Test { 
  val Pi = 3.14 
} 

While classes in similar languages extend entities that are called interfaces, Scala classes can extend traits. Scala's traits allow declaring both concrete fields and method implementations. In the following example, we declare the Logging trait, which outputs a custom error and warning messages using the abstract log method, and then mix the trait into the PrintLogging class:

trait Logging { 
  def log(s: String): Unit 
  def warn(s: String) = log("WARN: " + s) 
  def error(s: String) = log("ERROR: " + s) 
} 
class PrintLogging extends Logging { 
  def log(s: String) = println(s) 
} 

Classes can have type parameters. The following generic Pair class takes two type parameters, P and Q, which determines the types of its arguments, named first and second:

class Pair[P, Q](val first: P, val second: Q) 

Scala has support for first-class function objects, also called lambdas. In the following code snippet, we declare a twice lambda, which multiplies its argument by two:

val twice: Int => Int = (x: Int) => x * 2 

Tip

Downloading the example code:

You can download the example code files for all Packt books you have purchased from your account at http://www.packtpub.com. If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you.

In the preceding code, the (x: Int) part is the argument to the lambda, and x * 2 is its body. The => symbol must be placed between the arguments and the body of the lambda. The same => symbol is also used to express the type of the lambda, which is Int => Int, pronounced as Int to Int. In the preceding example, we can omit the type annotation Int => Int, and the compiler will infer the type of the twice lambda automatically, as shown in the following code:

val twice = (x: Int) => x * 2 

Alternatively, we can omit the type annotation in the lambda declaration and arrive at a more convenient syntax, as follows:

val twice: Int => Int = x => x * 2 

Finally, whenever the argument to the lambda appears only once in the body of the lambda, Scala allows a more convenient syntax, as follows:

val twice: Int => Int = _ * 2 

First-class functions allow manipulating blocks of code as if they were first-class values. They allow a more lightweight and concise syntax. In the following example, we use byname parameters to declare a runTwice method, which runs the specified block of code body twice:

def runTwice(body: =>Unit) = { 
  body 
  body 
} 

A byname parameter is formed by putting the => annotation before the type. Whenever the runTwice method references the body argument, the expression is re-evaluated, as shown in the following snippet:

runTwice { // this will print Hello twice 
  println("Hello") 
} 

Scala for expressions are a convenient way to traverse and transform collections. The following for loop prints the numbers in the range from 0 until 10; where 10 is not included in the range:

for (i <- 0 until 10) println(i) 

In the preceding code, the range is created with the expression 0 until 10; this is equivalent to the expression 0.until(10), which calls the method until on the value 0. In Scala, the dot notation can sometimes be dropped when invoking methods on objects.

Every for loop is equivalent to a foreach call. The preceding for loop is translated by the Scala compiler to the following expression:

(0 until 10).foreach(i => println(i)) 

  For-comprehensions are used to transform data. The following for-comprehension transforms all the numbers from 0 until 10 by multiplying them by -1:

val negatives = for (i <- 0 until 10) yield -i 

The negatives value contains negative numbers from 0 until -10. This for-comprehension is equivalent to the following map call:

val negatives = (0 until 10).map(i => -1 * i) 

It is also possible to transform data from multiple inputs. The following for-comprehension creates all pairs of integers between 0 and 4:

val pairs = for (x <- 0 until 4; y <- 0 until 4) yield (x, y) 

The preceding for-comprehension is equivalent to the following expression:

val pairs = (0 until 4).flatMap(x => (0 until 4).map(y => (x, y))) 

We can nest an arbitrary number of generator expressions in a for-comprehension. The Scala compiler will transform them into a sequence of nested flatMap calls, followed by a map call at the deepest level.

Commonly used Scala collections include sequences, denoted by the Seq[T] type; maps, denoted by the Map[K, V] type; and sets, denoted by the Set[T] type. In the following code, we create a sequence of strings:

val messages: Seq[String] = Seq("Hello", "World.", "!") 

Throughout this book, we rely heavily on the string interpolation feature. Normally, Scala strings are formed with double quotation marks. Interpolated strings are preceded with an s character, and can contain $ symbols with arbitrary identifiers resolved from the enclosing scope, as shown in the following example:

val magic = 7 
val myMagicNumber = s"My magic number is $magic" 

Pattern matching is another important Scala feature. For readers with Java, C#, or C background, a good way to describe it is to say that Scala's match statement is like the switch statement on steroids. The match statement can decompose arbitrary datatypes and allows you to express different cases in the program concisely.

In the following example, we declare a Map collection, named successors, used to map integers to their immediate successors. We then call the get method to obtain the successor of the number 5. The get method returns an object with the Option[Int] type, which may be implemented either with the Some class, indicating that the number 5 exists in the map, or the None class, indicating that the number 5 is not a key in the map. Pattern matching on the Option object allows proceeding casewise, as shown in the following code snippet:

val successors = Map(1 -> 2, 2 -> 3, 3 -> 4) 
successors.get(5) match { 
  case Some(n) => println(s"Successor is: $n") 
  case None    => println("Could not find successor.") 
} 

In Scala, most operators can be overloaded. Operator overloading is no different from declaring a method. In the following code snippet, we declare a Position class with a + operator:

class Position(val x: Int, val y: Int) { 
  def +(that: Position) = new Position(x + that.x, y + that.y) 
} 

Finally, Scala allows defining package objects to store top-level method and value definitions for a given package. In the following code snippet, we declare the package object for the org.learningconcurrency package. We implement the top level log method, which outputs a given string and the current thread name:

package org 
package object learningconcurrency { 
  def log(msg: String): Unit = 
    println(s"${Thread.currentThread.getName}: $msg") 
} 

We will use the log method in the examples throughout this book to trace how concurrent programs are executed.

This concludes our quick overview of important Scala features. If you would like to obtain a deeper knowledge about any of these language constructs, we suggest that you check out one of the introductory books on sequential programming in Scala.