Skip to content

Commit 9aaa3fa

Browse files
committed
Merge pull request #16 from ryblovAV/master
add answers to exercise 1-7 chapter 3
2 parents ae319fa + a2bac23 commit 9aaa3fa

File tree

14 files changed

+910
-0
lines changed

14 files changed

+910
-0
lines changed
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package org.learningconcurrency
2+
package exercises
3+
package ch3
4+
5+
6+
/**
7+
* Implement a custom ExecutionContext class called PiggybackContext,
8+
* which executes Runnable objects on the same thread that calls execute.
9+
* Ensure that a Runnable object executing on the PiggybackContext
10+
* can also call execute and that exceptions are properly reported.
11+
*/
12+
13+
import scala.util.{Failure, Success, Try}
14+
15+
object Ex1 extends App {
16+
17+
import scala.concurrent._
18+
19+
class PiggybackContext extends ExecutionContext {
20+
21+
override def execute(runnable: Runnable): Unit = Try(runnable.run()) match {
22+
case Success(r) => log("result: OK")
23+
case Failure(e) => reportFailure(e)
24+
}
25+
26+
override def reportFailure(cause: Throwable): Unit = {
27+
log(s"error: ${cause.getMessage}")
28+
}
29+
}
30+
31+
val e = new PiggybackContext
32+
33+
e.execute(new Runnable {
34+
override def run(): Unit = {
35+
log("run (exception)")
36+
throw new Exception("test exception")
37+
}
38+
})
39+
40+
e.execute(new Runnable {
41+
override def run(): Unit = {
42+
log("run")
43+
}
44+
})
45+
46+
47+
48+
49+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package org.learningconcurrency
2+
package exercises
3+
package ch3
4+
5+
/**
6+
* Implement a TreiberStack class, which implements a concurrent stack abstraction:
7+
* class TreiberStack[T] {
8+
* def push(x: T): Unit = ???
9+
* def pop(): T = ???
10+
* }
11+
* Use an atomic reference variable that points to a linked list of nodes that were previously pushed to the stack.
12+
* Make sure that your implementation is lock-free and not susceptible to the ABA problem.
13+
*/
14+
15+
import java.util.concurrent.atomic.AtomicReference
16+
17+
import scala.annotation.tailrec
18+
19+
object Ex2 extends App {
20+
21+
class TreiberStack[T] {
22+
23+
var r = new AtomicReference[List[T]](List.empty[T])
24+
25+
@tailrec
26+
final def push(x: T): Unit = {
27+
val oldList = r.get
28+
val newList = x::oldList
29+
if (!r.compareAndSet(oldList,newList)) push(x)
30+
}
31+
32+
@tailrec
33+
final def pop(): T = {
34+
val oldList = r.get
35+
val newList = oldList.tail
36+
if (r.compareAndSet(oldList,newList)) oldList.head
37+
else pop()
38+
}
39+
40+
}
41+
42+
import org.learningconcurrency.ch2._
43+
44+
val s = new TreiberStack[Int]
45+
46+
val t1 = thread {
47+
for (i <- 1 to 10) {
48+
s.push(i)
49+
Thread.sleep(1)
50+
}
51+
}
52+
53+
val t2 = thread {
54+
for (i <- 1 to 10) {
55+
s.push(i*10)
56+
Thread.sleep(1)
57+
}
58+
}
59+
60+
t1.join()
61+
t2.join()
62+
63+
for (i <- 1 to 20)
64+
log(s"s[$i] = ${s.pop()}")
65+
66+
67+
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package org.learningconcurrency
2+
package exercises
3+
package ch3
4+
5+
import java.util.concurrent.atomic.AtomicReference
6+
7+
import org.learningconcurrency.ch2._
8+
9+
import scala.annotation.tailrec
10+
11+
/**
12+
* Implement a ConcurrentSortedList class, which implements a concurrent
13+
*
14+
* class ConcurrentSortedList[T](implicit val ord: Ordering[T]) {
15+
* def add(x: T): Unit = ???
16+
* def iterator: Iterator[T] = ???
17+
* }
18+
*
19+
* Under the hood, the ConcurrentSortedList class should use a linked list of atomic references.
20+
* Ensure that your implementation is lock-free and avoids ABA problems.
21+
* The Iterator object returned by the iterator method must correctly traverse the elements of the list
22+
* in the ascending order under the assumption that there are no concurrent invocations of the add method.
23+
*
24+
* If required, modify the ConcurrentSortedList class from the previous example so
25+
* that calling the add method has the running time linear to the length of the list
26+
* and creates a constant number of new objects when there are no retries due to concurrent add invocations.
27+
*/
28+
29+
object Ex3_4 extends App {
30+
31+
class ConcurrentSortedList[T](implicit val ord: Ordering[T]) {
32+
33+
case class Node(head: T,
34+
tail: AtomicReference[Option[Node]] = new AtomicReference[Option[Node]](None))
35+
36+
val root = new AtomicReference[Option[Node]](None)
37+
38+
@tailrec
39+
private def add(r: AtomicReference[Option[Node]], x: T): Unit = {
40+
val optNode = r.get
41+
42+
optNode match {
43+
case None => {
44+
if (!r.compareAndSet(optNode, Some(Node(x)))) add(r, x)
45+
}
46+
case Some(Node(head, tail)) =>
47+
if (ord.compare(x, head) > 0) {
48+
//prepend new node
49+
val newNode = Node(x)
50+
newNode.tail.set(optNode)
51+
if (!r.compareAndSet(optNode, Some(newNode))) add(r, x)
52+
} else {
53+
//add to tail
54+
add(tail, x)
55+
}
56+
}
57+
}
58+
59+
def add(x: T): Unit = {
60+
add(root, x)
61+
}
62+
63+
def iterator: Iterator[T] = new Iterator[T] {
64+
65+
var rIter = root.get
66+
67+
override def hasNext: Boolean = rIter.isEmpty == false
68+
69+
override def next(): T = {
70+
71+
rIter match {
72+
case Some(node) => {
73+
rIter = node.tail.get
74+
node.head
75+
}
76+
case None => throw new NoSuchElementException("next on empty iterator")
77+
}
78+
}
79+
}
80+
}
81+
82+
val csl = new ConcurrentSortedList[Int]()
83+
84+
85+
(1 to 100).map((i) => thread {
86+
Thread.sleep((Math.random() * 100).toInt)
87+
for (i <- 1 to 1000) {
88+
Thread.sleep((Math.random() * 10).toInt)
89+
csl.add((math.random * 100 + i).toInt)
90+
}
91+
}
92+
).foreach(_.join)
93+
94+
log(s"length = ${csl.iterator.length}")
95+
96+
var prev = 0
97+
var length = 0
98+
for (a <- csl.iterator) {
99+
log(a.toString)
100+
if (prev > a) throw new Exception(s"$prev > $a")
101+
length += 1
102+
}
103+
104+
if (csl.iterator.length != length) throw new Exception(s"${csl.iterator.length} != $length")
105+
106+
log(s"length = ${csl.iterator.length} ($length)")
107+
108+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package org.learningconcurrency
2+
package exercises
3+
package ch3
4+
5+
/**
6+
* Implement a LazyCell class with the following interface:
7+
*
8+
* class LazyCell[T](initialization: =>T) {
9+
* def apply(): T = ???
10+
* }
11+
*
12+
* Creating a LazyCell object and calling the apply method must have the
13+
* same semantics as declaring a lazy value and reading it, respectively.
14+
* You are not allowed to use lazy values in your implementation.
15+
*
16+
*/
17+
18+
object Ex5 extends App {
19+
20+
class LazyCellWithLazy[T](initialization: => T) {
21+
lazy val l = initialization
22+
}
23+
24+
class LazyCell[T](initialization: => T) {
25+
26+
@volatile
27+
var r: Option[T] = None
28+
29+
def apply(): T = r match {
30+
case Some(v) => v
31+
case None => this synchronized {
32+
r match {
33+
case Some(v) => v
34+
case None => {
35+
r = Some(initialization)
36+
r.get
37+
}
38+
}
39+
}
40+
}
41+
}
42+
43+
def func = {
44+
log("start...")
45+
Thread.sleep(10000)
46+
s"Calculation by ${Thread.currentThread().getName}"
47+
}
48+
49+
val a = new LazyCell[String](func)
50+
51+
import org.learningconcurrency.ch2._
52+
53+
log("Start")
54+
55+
val b = new LazyCellWithLazy[String](func)
56+
57+
(0 to 50).
58+
map((i) => thread({
59+
Thread.sleep((Math.random * 10).toInt)
60+
println(a.apply)
61+
})).
62+
foreach(_.join)
63+
64+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package org.learningconcurrency
2+
package exercises
3+
package ch3
4+
5+
import java.util.concurrent.atomic.AtomicReference
6+
7+
import scala.annotation.tailrec
8+
9+
/**
10+
* Implement a PureLazyCell class with the same interface and semantics as the LazyCell class from the previous exercise.
11+
* The PureLazyCell class assumes that the initialization parameter does not cause side effects,
12+
* so it can be evaluated more than once.
13+
* The apply method must be lock-free and should call the initialization as little as possible.
14+
*/
15+
16+
object Ex6 extends App {
17+
18+
class PureLazyCell[T](initialization: => T) {
19+
20+
val r = new AtomicReference[Option[T]](None)
21+
22+
@tailrec
23+
final def apply(): T = r.get match {
24+
case Some(v) => v
25+
case None => {
26+
val v = initialization
27+
if (!r.compareAndSet(None, Some(v))) apply()
28+
else v
29+
}
30+
}
31+
}
32+
33+
def initialization = {
34+
log("calculation ...")
35+
Thread.sleep(1000)
36+
s"result (calculate by ${Thread.currentThread().getName})"
37+
}
38+
39+
val p = new PureLazyCell[String](initialization)
40+
41+
import org.learningconcurrency.ch2.thread
42+
43+
log("start")
44+
45+
val t = (1 to 10).map((i) => thread {
46+
val sleep = (Math.random * 10000).toInt
47+
Thread.sleep(sleep)
48+
49+
(1 to 3).foreach((i) => log(s"v$i = ${p.apply}"))
50+
})
51+
52+
t.foreach(_.join)
53+
54+
}

0 commit comments

Comments
 (0)