-
Notifications
You must be signed in to change notification settings - Fork 105
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
axel22
merged 13 commits into
concurrent-programming-in-scala:master
from
ryblovAV:master
Sep 14, 2015
Merged
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
943d513
add answers to exercise 1-7 chapter 3
ryblovAV f6ae264
add tailrace annotation
ryblovAV 51ec10c
use atomic reference of a linked list insteadoff linked list of atomi…
ryblovAV 428959c
make the field r volatile
ryblovAV f86d284
add answers to exercise 1-8 chapter 4
ryblovAV f55f24c
change implementation (add inner class Node)
ryblovAV be16487
remove blocking and isCompleted
ryblovAV d40d2a6
reformat code
ryblovAV 8aaee6b
delete recover
ryblovAV 6bf5077
avoid thread starvation
ryblovAV 33bf737
set method private
ryblovAV bc71621
add throws the exception if a key already assigned
ryblovAV a2bac23
fix call apply method
ryblovAV File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
49 changes: 49 additions & 0 deletions
49
src/main/scala/org/learningconcurrency/exercises/ch3/ex1.scala
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
67
src/main/scala/org/learningconcurrency/exercises/ch3/ex2.scala
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
108
src/main/scala/org/learningconcurrency/exercises/ch3/ex3_4.scala
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
64
src/main/scala/org/learningconcurrency/exercises/ch3/ex5.scala
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
|
||
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
54
src/main/scala/org/learningconcurrency/exercises/ch3/ex6.scala
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
|
||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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 thethis
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 fieldr
, then there is no guarantee that it will see all the updates that some other thread T1 had done before writingx
tor
.In general, JVM can reorder the instructions so that the freshly allocated
initialization
object of typeT
gets assigned before its constructor code actually ran.In this particular case,
r
will point to aSome(t)
, which is an immutable object, so the JVM is not allowed to move theinitialization
expression code after the assignment of the memory reference to ther
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.