Skip to content

add answers to exercise 1-7 chapter 3 #16

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Sep 14, 2015
49 changes: 49 additions & 0 deletions src/main/scala/org/learningconcurrency/exercises/ch3/ex1.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package org.learningconcurrency
package exercises
package ch3


/**
* Implement a custom ExecutionContext class called PiggybackContext,
* which executes Runnable objects on the same thread that calls execute.
* Ensure that a Runnable object executing on the PiggybackContext
* can also call execute and that exceptions are properly reported.
*/

import scala.util.{Failure, Success, Try}

object Ex1 extends App {

import scala.concurrent._

class PiggybackContext extends ExecutionContext {

override def execute(runnable: Runnable): Unit = Try(runnable.run()) match {
case Success(r) => log("result: OK")
case Failure(e) => reportFailure(e)
}

override def reportFailure(cause: Throwable): Unit = {
log(s"error: ${cause.getMessage}")
}
}

val e = new PiggybackContext

e.execute(new Runnable {
override def run(): Unit = {
log("run (exception)")
throw new Exception("test exception")
}
})

e.execute(new Runnable {
override def run(): Unit = {
log("run")
}
})




}
67 changes: 67 additions & 0 deletions src/main/scala/org/learningconcurrency/exercises/ch3/ex2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package org.learningconcurrency
package exercises
package ch3

/**
* Implement a TreiberStack class, which implements a concurrent stack abstraction:
* class TreiberStack[T] {
* def push(x: T): Unit = ???
* def pop(): T = ???
* }
* Use an atomic reference variable that points to a linked list of nodes that were previously pushed to the stack.
* Make sure that your implementation is lock-free and not susceptible to the ABA problem.
*/

import java.util.concurrent.atomic.AtomicReference

import scala.annotation.tailrec

object Ex2 extends App {

class TreiberStack[T] {

var r = new AtomicReference[List[T]](List.empty[T])

@tailrec
final def push(x: T): Unit = {
val oldList = r.get
val newList = x::oldList
if (!r.compareAndSet(oldList,newList)) push(x)
}

@tailrec
final def pop(): T = {
val oldList = r.get
val newList = oldList.tail
if (r.compareAndSet(oldList,newList)) oldList.head
else pop()
}

}

import org.learningconcurrency.ch2._

val s = new TreiberStack[Int]

val t1 = thread {
for (i <- 1 to 10) {
s.push(i)
Thread.sleep(1)
}
}

val t2 = thread {
for (i <- 1 to 10) {
s.push(i*10)
Thread.sleep(1)
}
}

t1.join()
t2.join()

for (i <- 1 to 20)
log(s"s[$i] = ${s.pop()}")


}
108 changes: 108 additions & 0 deletions src/main/scala/org/learningconcurrency/exercises/ch3/ex3_4.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package org.learningconcurrency
package exercises
package ch3

import java.util.concurrent.atomic.AtomicReference

import org.learningconcurrency.ch2._

import scala.annotation.tailrec

/**
* Implement a ConcurrentSortedList class, which implements a concurrent
*
* class ConcurrentSortedList[T](implicit val ord: Ordering[T]) {
* def add(x: T): Unit = ???
* def iterator: Iterator[T] = ???
* }
*
* Under the hood, the ConcurrentSortedList class should use a linked list of atomic references.
* Ensure that your implementation is lock-free and avoids ABA problems.
* The Iterator object returned by the iterator method must correctly traverse the elements of the list
* in the ascending order under the assumption that there are no concurrent invocations of the add method.
*
* If required, modify the ConcurrentSortedList class from the previous example so
* that calling the add method has the running time linear to the length of the list
* and creates a constant number of new objects when there are no retries due to concurrent add invocations.
*/

object Ex3_4 extends App {

class ConcurrentSortedList[T](implicit val ord: Ordering[T]) {

case class Node(head: T,
tail: AtomicReference[Option[Node]] = new AtomicReference[Option[Node]](None))

val root = new AtomicReference[Option[Node]](None)

@tailrec
private def add(r: AtomicReference[Option[Node]], x: T): Unit = {
val optNode = r.get

optNode match {
case None => {
if (!r.compareAndSet(optNode, Some(Node(x)))) add(r, x)
}
case Some(Node(head, tail)) =>
if (ord.compare(x, head) > 0) {
//prepend new node
val newNode = Node(x)
newNode.tail.set(optNode)
if (!r.compareAndSet(optNode, Some(newNode))) add(r, x)
} else {
//add to tail
add(tail, x)
}
}
}

def add(x: T): Unit = {
add(root, x)
}

def iterator: Iterator[T] = new Iterator[T] {

var rIter = root.get

override def hasNext: Boolean = rIter.isEmpty == false

override def next(): T = {

rIter match {
case Some(node) => {
rIter = node.tail.get
node.head
}
case None => throw new NoSuchElementException("next on empty iterator")
}
}
}
}

val csl = new ConcurrentSortedList[Int]()


(1 to 100).map((i) => thread {
Thread.sleep((Math.random() * 100).toInt)
for (i <- 1 to 1000) {
Thread.sleep((Math.random() * 10).toInt)
csl.add((math.random * 100 + i).toInt)
}
}
).foreach(_.join)

log(s"length = ${csl.iterator.length}")

var prev = 0
var length = 0
for (a <- csl.iterator) {
log(a.toString)
if (prev > a) throw new Exception(s"$prev > $a")
length += 1
}

if (csl.iterator.length != length) throw new Exception(s"${csl.iterator.length} != $length")

log(s"length = ${csl.iterator.length} ($length)")

}
64 changes: 64 additions & 0 deletions src/main/scala/org/learningconcurrency/exercises/ch3/ex5.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package org.learningconcurrency
package exercises
package ch3

/**
* Implement a LazyCell class with the following interface:
*
* class LazyCell[T](initialization: =>T) {
* def apply(): T = ???
* }
*
* Creating a LazyCell object and calling the apply method must have the
* same semantics as declaring a lazy value and reading it, respectively.
* You are not allowed to use lazy values in your implementation.
*
*/

object Ex5 extends App {

class LazyCellWithLazy[T](initialization: => T) {
lazy val l = initialization
}

class LazyCell[T](initialization: => T) {

@volatile
var r: Option[T] = None
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Say that there is some thread T2 that acquires the this monitor after the thread T1 releases the this monitor.
Then, all the changes by thread T1 have to be visible to the thread T2.

However, if there is a thread T2 that does not acquire the this monitor and reads the field r, then there is no guarantee that it will see all the updates that some other thread T1 had done before writing x to r.
In general, JVM can reorder the instructions so that the freshly allocated initialization object of type T gets assigned before its constructor code actually ran.

In this particular case, r will point to a Some(t), which is an immutable object, so the JVM is not allowed to move the initialization expression code after the assignment of the memory reference to the r field.

So this code seems correct to me, but its correctness is very subtle. This is why I would still make the field r volatile.


def apply(): T = r match {
case Some(v) => v
case None => this synchronized {
r match {
case Some(v) => v
case None => {
r = Some(initialization)
r.get
}
}
}
}
}

def func = {
log("start...")
Thread.sleep(10000)
s"Calculation by ${Thread.currentThread().getName}"
}

val a = new LazyCell[String](func)

import org.learningconcurrency.ch2._

log("Start")

val b = new LazyCellWithLazy[String](func)

(0 to 50).
map((i) => thread({
Thread.sleep((Math.random * 10).toInt)
println(a.apply)
})).
foreach(_.join)

}
54 changes: 54 additions & 0 deletions src/main/scala/org/learningconcurrency/exercises/ch3/ex6.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package org.learningconcurrency
package exercises
package ch3

import java.util.concurrent.atomic.AtomicReference

import scala.annotation.tailrec

/**
* Implement a PureLazyCell class with the same interface and semantics as the LazyCell class from the previous exercise.
* The PureLazyCell class assumes that the initialization parameter does not cause side effects,
* so it can be evaluated more than once.
* The apply method must be lock-free and should call the initialization as little as possible.
*/

object Ex6 extends App {

class PureLazyCell[T](initialization: => T) {

val r = new AtomicReference[Option[T]](None)

@tailrec
final def apply(): T = r.get match {
case Some(v) => v
case None => {
val v = initialization
if (!r.compareAndSet(None, Some(v))) apply()
else v
}
}
}

def initialization = {
log("calculation ...")
Thread.sleep(1000)
s"result (calculate by ${Thread.currentThread().getName})"
}

val p = new PureLazyCell[String](initialization)

import org.learningconcurrency.ch2.thread

log("start")

val t = (1 to 10).map((i) => thread {
val sleep = (Math.random * 10000).toInt
Thread.sleep(sleep)

(1 to 3).foreach((i) => log(s"v$i = ${p.apply}"))
})

t.foreach(_.join)

}
Loading