-
Notifications
You must be signed in to change notification settings - Fork 104
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
Changes from 5 commits
943d513
f6ae264
51ec10c
428959c
f86d284
f55f24c
be16487
d40d2a6
8aaee6b
6bf5077
33bf737
bc71621
a2bac23
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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") | ||
} | ||
}) | ||
|
||
|
||
|
||
|
||
} |
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()}") | ||
|
||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
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]) { | ||
|
||
private val h: AtomicReference[Option[T]] = new AtomicReference[Option[T]](None) | ||
private val t: AtomicReference[Option[ConcurrentSortedList[T]]] = new AtomicReference[Option[ConcurrentSortedList[T]]](None) | ||
|
||
@tailrec | ||
private def addToTail(x: T): Unit = { | ||
val v = t.get | ||
v match { | ||
case Some(p) => p.add(x) | ||
case None => { | ||
val l = new ConcurrentSortedList[T] | ||
l.h.set(Some(x)) | ||
if (!t.compareAndSet(v, Some(l))) addToTail(x) | ||
} | ||
} | ||
} | ||
|
||
@tailrec | ||
final def add(x: T): Unit = { | ||
val optV = h.get | ||
|
||
optV match { | ||
case Some(v) => { | ||
if (ord.compare(v, x) >= 0) { | ||
if (h.compareAndSet(optV, Some(x))) addToTail(v) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. With this Also, adding like this will cause an unnecessary ripple effect, where the values are shifted to the end of the list, instead of just prepending one node to the beginning of the list (you need one CAS for the insert in the best solution, here you can have up to |
||
else add(x) | ||
} else addToTail(x) | ||
} | ||
case None => { | ||
if (!h.compareAndSet(optV, Some(x))) | ||
add(x) | ||
} | ||
} | ||
} | ||
|
||
def iterator: Iterator[T] = new Iterator[T] { | ||
|
||
var rIter: Option[ConcurrentSortedList[T]] = Some(ConcurrentSortedList.this) | ||
|
||
override def hasNext: Boolean = rIter.isEmpty == false | ||
|
||
override def next(): T = { | ||
|
||
rIter match { | ||
case Some(r) => { | ||
rIter = r.t.get | ||
r.h.get match { | ||
case Some(v) => v | ||
case None => throw new NoSuchElementException("next on empty iterator") | ||
} | ||
} | ||
case None => throw new NoSuchElementException("next on empty iterator") | ||
} | ||
} | ||
} | ||
|
||
} | ||
|
||
val csl = new ConcurrentSortedList[Int]() | ||
|
||
|
||
(1 to 10).map((i) => thread { | ||
Thread.sleep((Math.random() * 100).toInt) | ||
for (i <- 1 to 10) | ||
csl.add((math.random * 100 + i).toInt) | ||
} | ||
).foreach(_.join) | ||
|
||
log(s"length = ${csl.iterator.length}") | ||
|
||
for (a <- csl.iterator) { | ||
log(a.toString) | ||
} | ||
|
||
} |
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Say that there is some thread T2 that acquires the However, if there is a thread T2 that does not acquire the In this particular case, So this code seems correct to me, but its correctness is very subtle. This is why I would still make the field |
||
|
||
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) | ||
|
||
} |
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) | ||
|
||
} |
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.
I would model the list with the following inner class:
The
ConcurrentSortedList
would then contain a single reference:This would make the implementation of the
add
method simpler, and easier to reason about. There would be no need to callset
inaddToTail
, since you set it only once anyway.