-
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 1 commit
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,63 @@ | ||
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 | ||
|
||
object Ex2 extends App { | ||
|
||
class TreiberStack[T] { | ||
|
||
var r = new AtomicReference[List[T]](List.empty[T]) | ||
|
||
def push(x: T): Unit = { | ||
val oldList = r.get | ||
val newList = x::oldList | ||
if (!r.compareAndSet(oldList,newList)) push(x) | ||
} | ||
|
||
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,85 @@ | ||
package org.learningconcurrency | ||
package exercises | ||
package ch3 | ||
|
||
import java.util.ConcurrentModificationException | ||
import java.util.concurrent.atomic.AtomicReference | ||
|
||
import org.learningconcurrency.ch2._ | ||
|
||
/** | ||
* 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]) { | ||
|
||
val r = new AtomicReference[List[T]](List.empty[T]) | ||
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. Note that the concurrent sorted list should use a linked list of atomic references, not an atomic reference of a linked list. The two are different, since a linked list of atomic references has a lower contention - adding an element requires only a CAS in the node where the element should be added. Also, the current solution with |
||
|
||
def addWithSort(x:T,l:List[T]):List[T] = l match { | ||
case h::_ if ord.compare(h,x) >= 0 => x::l | ||
case h::t => h::addWithSort(x,t) | ||
case _ => List(x) | ||
} | ||
|
||
def add(x: T): Unit = { | ||
val oldList = r.get | ||
val newList = addWithSort(x,oldList) | ||
|
||
if (!r.compareAndSet(oldList,newList)) add(x) | ||
} | ||
|
||
def iterator: Iterator[T] = new Iterator[T] { | ||
|
||
val rIter = new AtomicReference(r.get) | ||
|
||
override def hasNext: Boolean = { | ||
!rIter.get.isEmpty | ||
} | ||
|
||
override def next(): T = { | ||
val l = rIter.get | ||
|
||
if (l.isEmpty) throw new NoSuchElementException("next on empty iterator") | ||
|
||
if (!rIter.compareAndSet(l,l.tail)) | ||
throw new ConcurrentModificationException() | ||
|
||
l.head | ||
} | ||
|
||
} | ||
|
||
} | ||
|
||
val csl = new ConcurrentSortedList[Int]() | ||
|
||
|
||
(1 to 10).map((i) => thread { | ||
for (i <- 1 to 10) | ||
csl.add((math.random * 100 + i).toInt) | ||
Thread.sleep(1) | ||
} | ||
).foreach(_.join) | ||
|
||
for (a <- csl.iterator) { | ||
log(a.toString) | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
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) { | ||
|
||
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,51 @@ | ||
package org.learningconcurrency | ||
package exercises | ||
package ch3 | ||
|
||
import java.util.concurrent.atomic.AtomicReference | ||
|
||
/** | ||
* 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) | ||
|
||
def apply(): T = r.get match { | ||
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. This can be made |
||
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.
You can require that this method is tail recursive, to avoid the danger of a stack overflow.