├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── build.sbt ├── concurrency-scala-book.jpg ├── project └── plugins.sbt └── src └── main ├── resources └── org │ ├── .DS_Store │ └── learningconcurrency │ └── words.txt └── scala └── org └── learningconcurrency ├── ch2 ├── JavaMemoryModel.scala ├── Synchronized.scala ├── Threads.scala ├── Unsafe.scala ├── Volatile.scala └── package.scala ├── ch3 ├── Atomic.scala ├── Collections.scala ├── Executors.scala ├── FileSystem.scala ├── LazyVals.scala ├── LockFreePool.scala ├── Processes.scala └── package.scala ├── ch4 ├── Alternative.scala ├── Async.scala ├── Blocking.scala ├── Futures.scala └── Promises.scala ├── ch5 ├── Blitz.scala ├── Concurrent.scala ├── Custom.scala ├── Par.scala └── package.scala ├── ch6 ├── Alternative.scala ├── Composition.scala ├── Observables.scala ├── Schedulers.scala ├── Subjects.scala └── package.scala ├── ch7 ├── Atomic.scala ├── Composition.scala ├── Retry.scala └── TransactionalCollections.scala ├── ch8 ├── Actors.scala ├── Communicating.scala ├── Remoting.scala ├── Supervision.scala └── package.scala ├── ch9 ├── Debugging.scala ├── FTPClient.scala ├── FTPServer.scala └── package.scala ├── exercises ├── ch1 │ ├── ex1.scala │ ├── ex2.scala │ ├── ex3.scala │ └── ex4.scala ├── ch2 │ ├── ex1.scala │ ├── ex10.scala │ ├── ex2.scala │ ├── ex3.scala │ ├── ex4.scala │ ├── ex5.scala │ ├── ex6.scala │ ├── ex7.scala │ ├── ex8.scala │ └── ex9.scala ├── ch3 │ ├── ex1.scala │ ├── ex2.scala │ ├── ex3_4.scala │ ├── ex5.scala │ ├── ex6.scala │ ├── ex7.scala │ ├── ex8.scala │ └── ex8_evaluationapp.scala ├── ch4 │ ├── ex1.scala │ ├── ex2.scala │ ├── ex3.scala │ ├── ex4.scala │ ├── ex5.scala │ ├── ex6.scala │ ├── ex7.scala │ └── ex8.scala ├── ch5 │ ├── Ex2.scala │ └── ex1.scala ├── ch6 │ ├── Ex1.scala │ ├── Ex2.scala │ ├── Ex3.scala │ ├── Ex4.scala │ ├── Ex5.scala │ ├── Ex6.scala │ └── Ex7.scala ├── ch7 │ ├── ex1.scala │ ├── ex2.scala │ ├── ex3.scala │ ├── ex4.scala │ ├── ex5.scala │ └── ex6.scala ├── ch8 │ ├── ex1.scala │ ├── ex2.scala │ ├── ex3.scala │ ├── ex4.scala │ ├── ex5.scala │ └── ex6.scala └── package.scala └── package.scala /.gitignore: -------------------------------------------------------------------------------- 1 | # intellij 2 | *.iml 3 | .idea 4 | 5 | # ensime 6 | .ensime 7 | .ensime_cache 8 | 9 | # sbt output 10 | target 11 | SCALA-README.md 12 | url-spec.txt 13 | 14 | 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: scala 2 | scala: 3 | - "2.11.4" 4 | jdk: 5 | - oraclejdk7 6 | script: sbt "++ ${TRAVIS_SCALA_VERSION} ; test" 7 | branches: 8 | only: 9 | - master 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2014, Aleksandar Prokopec 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software 4 | and associated documentation files (the "Software"), to deal in the Software without restriction, 5 | including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 6 | and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 7 | subject to the following conditions: 8 | 9 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 12 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 13 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 14 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 15 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR 16 | THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Learning Concurrent Programming in Scala 2 | ======================================== 3 | 4 | [![Build Status](https://travis-ci.org/concurrent-programming-in-scala/learning-examples.svg?branch=master)](https://travis-ci.org/concurrent-programming-in-scala/learning-examples) 5 | 6 | This repository contains the complete examples for the books Learning Concurrent Programming in Scala, published by Packt. 7 | 8 | [![Packt Concurrency](concurrency-scala-book.jpg)](https://www.packtpub.com/application-development/learning-concurrent-programming-scala) 9 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | 2 | name := "concurrency-examples" 3 | 4 | version := "1.0" 5 | 6 | scalaVersion in ThisBuild := "2.11.1" 7 | 8 | ensimeScalaVersion in ThisBuild := "2.11.1" 9 | 10 | resolvers ++= Seq( 11 | "Sonatype OSS Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots", 12 | "Sonatype OSS Releases" at "https://oss.sonatype.org/content/repositories/releases", 13 | "Typesafe Repository" at "http://repo.typesafe.com/typesafe/releases/" 14 | ) 15 | 16 | fork := false 17 | 18 | libraryDependencies += "commons-io" % "commons-io" % "2.6" 19 | 20 | libraryDependencies += "org.scala-lang.modules" %% "scala-async" % "0.9.1" 21 | 22 | libraryDependencies += "com.github.scala-blitz" %% "scala-blitz" % "1.2" 23 | 24 | libraryDependencies += "com.netflix.rxjava" % "rxjava-scala" % "0.19.1" 25 | 26 | libraryDependencies += "org.scala-lang.modules" %% "scala-swing" % "1.0.1" 27 | 28 | libraryDependencies += "org.scala-stm" %% "scala-stm" % "0.7" 29 | 30 | libraryDependencies += "com.typesafe.akka" %% "akka-actor" % "2.3.2" 31 | 32 | libraryDependencies += "com.typesafe.akka" %% "akka-remote" % "2.3.2" 33 | 34 | libraryDependencies += "com.storm-enroute" %% "scalameter-core" % "0.6" 35 | 36 | libraryDependencies += "org.scalaz" %% "scalaz-concurrent" % "7.0.6" 37 | 38 | libraryDependencies += "com.typesafe.akka" %% "akka-stream-experimental" % "0.4" 39 | 40 | libraryDependencies += "com.quantifind" %% "wisp" % "0.0.4" 41 | 42 | libraryDependencies += "org.scalafx" %% "scalafx" % "1.0.0-R8" 43 | 44 | unmanagedJars in Compile += Attributed.blank(file(scala.util.Properties.javaHome) / "/lib/jfxrt.jar") 45 | 46 | libraryDependencies += "com.storm-enroute" %% "reactive-collections" % "0.5" -------------------------------------------------------------------------------- /concurrency-scala-book.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/concurrent-programming-in-scala/learning-examples/95f2b120809ccf9d92a287a5fb07bdbe228634ce/concurrency-scala-book.jpg -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | 2 | addSbtPlugin("org.ensime" % "sbt-ensime" % "1.11.0") 3 | -------------------------------------------------------------------------------- /src/main/resources/org/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/concurrent-programming-in-scala/learning-examples/95f2b120809ccf9d92a287a5fb07bdbe228634ce/src/main/resources/org/.DS_Store -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/ch2/JavaMemoryModel.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package ch2 3 | 4 | 5 | 6 | 7 | 8 | 9 | object JMMPublicationWrong extends App { 10 | class Num(var x: Int) 11 | var num = new Num(-1) 12 | val t = thread { 13 | for (i <- 1 until 10000000) num = new Num(i) 14 | } 15 | while (t.isAlive) assert(num.x != 0) 16 | } 17 | 18 | 19 | object JMMPublicationRight extends App { 20 | class Num(val x: Int) 21 | @volatile var num = new Num(-1) 22 | val t = thread { 23 | for (i <- 1 until 10000000) num = new Num(i) 24 | } 25 | while (t.isAlive) assert(num.x != 0) 26 | } 27 | 28 | // rules for happens-before are: 29 | // Program order rule. Each action in a thread happens-before every ac- 30 | // tion in that thread that comes later in the program order. 31 | // Monitor lock rule. An unlock on a monitor lock happens-before every 32 | // subsequent lock on that same monitor lock.3 33 | // Volatile variable rule. A write to a volatile field happens-before every 34 | // subsequent read of that same field.4 35 | // Thread start rule. A call to Thread.start on a thread happens-before 36 | // every action in the started thread. 37 | // Thread termination rule. Any action in a thread happens-before any other thread detects that thread has terminated, either by success- fully return from Thread.join or by Thread.isAlive returning false. 38 | // Interruption rule. A thread calling interrupt on another thread happens-before the interrupted thread detects the interrupt (either by having InterruptedException thrown, or invoking isInter- rupted or interrupted). 39 | // Finalizer rule. The end of a constructor for an object happens-before the start of the finalizer for that object. 40 | // Transitivity. If A happens-before B, and B happens-before C, then A happens-before C. 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/ch2/Synchronized.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package ch2 3 | 4 | 5 | 6 | 7 | 8 | 9 | object SynchronizedProtectedUid extends App { 10 | 11 | var uidCount = 0L 12 | 13 | def getUniqueId() = this.synchronized { 14 | val freshUid = uidCount + 1 15 | uidCount = freshUid 16 | freshUid 17 | } 18 | 19 | def printUniqueIds(n: Int): Unit = { 20 | val uids = for (i <- 0 until n) yield getUniqueId() 21 | println(s"Generated uids: $uids") 22 | } 23 | 24 | val t = thread{ 25 | printUniqueIds(5) 26 | } 27 | printUniqueIds(5) 28 | t.join() 29 | 30 | } 31 | 32 | 33 | // we should skip this one 34 | object SynchronizedSharedStateAccess extends App { 35 | for (i <- 0 until 10000) { 36 | var t1started = false 37 | var t2started = false 38 | var t1index = 0 39 | var t2index = 0 40 | 41 | val t1 = thread { 42 | Thread.sleep(1) 43 | this.synchronized { t1started = true } 44 | val t2s = this.synchronized { t2started } 45 | t2index = if (t2started) 0 else 1 46 | } 47 | val t2 = thread { 48 | Thread.sleep(1) 49 | this.synchronized { t2started = true } 50 | val t1s = this.synchronized { t1started } 51 | t1index = if (t1s) 0 else 1 52 | } 53 | 54 | t1.join() 55 | t2.join() 56 | assert(!(t1index == 1 && t2index == 1), s"t1 = $t1index, t2 = $t2index") 57 | } 58 | } 59 | 60 | 61 | object SynchronizedNesting extends App { 62 | import scala.collection._ 63 | private val transfers = mutable.ArrayBuffer[String]() 64 | def logTransfer(name: String, n: Int): Unit = transfers.synchronized { 65 | transfers += s"transfer to account '$name' = $n" 66 | } 67 | class Account(val name: String, var money: Int) 68 | def add(account: Account, n: Int) = account.synchronized { 69 | account.money += n 70 | if (n > 10) logTransfer(account.name, n) 71 | } 72 | val jane = new Account("Jane", 100) 73 | val john = new Account("John", 200) 74 | val t1 = thread { add(jane, 5) } 75 | val t2 = thread { add(john, 50) } 76 | val t3 = thread { add(jane, 70) } 77 | t1.join(); t2.join(); t3.join() 78 | log(s"--- transfers ---\n$transfers") 79 | } 80 | 81 | 82 | object SynchronizedDeadlock extends App { 83 | import SynchronizedNesting.Account 84 | def send(a: Account, b: Account, n: Int) = a.synchronized { 85 | b.synchronized { 86 | a.money -= n 87 | b.money += n 88 | } 89 | } 90 | val a = new Account("Jill", 1000) 91 | val b = new Account("Jack", 2000) 92 | val t1 = thread { for (i <- 0 until 100) send(a, b, 1) } 93 | val t2 = thread { for (i <- 0 until 100) send(b, a, 1) } 94 | t1.join() 95 | t2.join() 96 | log(s"a = ${a.money}, b = ${b.money}") 97 | } 98 | 99 | 100 | object SynchronizedNoDeadlock extends App { 101 | import SynchronizedProtectedUid._ 102 | class Account(val name: String, var money: Int) { 103 | val uid = getUniqueId() 104 | } 105 | def send(a1: Account, a2: Account, n: Int) { 106 | def adjust() { 107 | a1.money -= n 108 | a2.money += n 109 | } 110 | if (a1.uid < a2.uid) 111 | a1.synchronized { a2.synchronized { adjust() } } 112 | else 113 | a2.synchronized { a1.synchronized { adjust() } } 114 | } 115 | val a = new Account("Jill", 1000) 116 | val b = new Account("Jack", 2000) 117 | val t1 = thread { for (i <- 0 until 100) send(a, b, 1) } 118 | val t2 = thread { for (i <- 0 until 100) send(b, a, 1) } 119 | t1.join() 120 | t2.join() 121 | log(s"a = ${a.money}, b = ${b.money}") 122 | } 123 | 124 | 125 | object SynchronizedDuplicates extends App { 126 | import scala.collection._ 127 | val duplicates = mutable.Set[Int]() 128 | val numbers = mutable.ArrayBuffer[Int]() 129 | def isDuplicate(n: Int): Unit = duplicates.synchronized { 130 | duplicates.contains(n) 131 | } 132 | def addDuplicate(n: Int): Unit = duplicates.synchronized { 133 | duplicates += n 134 | } 135 | def addNumber(n: Int): Unit = numbers.synchronized { 136 | numbers += n 137 | if (numbers.filter(_ == n).size > 1) addDuplicate(n) 138 | } 139 | val threads = for (i <- 1 to 2) yield thread { 140 | for (n <- 0 until i * 10) addNumber(n) 141 | } 142 | for (t <- threads) t.join() 143 | println(duplicates.mkString("\n")) 144 | } 145 | 146 | 147 | object SynchronizedBadPool extends App { 148 | import scala.collection._ 149 | private val tasks = mutable.Queue[() => Unit]() 150 | val worker = new Thread { 151 | def poll(): Option[() => Unit] = tasks.synchronized { 152 | if (tasks.nonEmpty) Some(tasks.dequeue()) else None 153 | } 154 | override def run() = while (true) poll() match { 155 | case Some(task) => task() 156 | case None => 157 | } 158 | } 159 | worker.setDaemon(true) 160 | worker.start() 161 | 162 | def asynchronous(body: =>Unit) = tasks.synchronized { 163 | tasks.enqueue(() => body) 164 | } 165 | 166 | asynchronous { log("Hello") } 167 | asynchronous { log(" world!")} 168 | Thread.sleep(100) 169 | } 170 | 171 | 172 | object SynchronizedGuardedBlocks extends App { 173 | val lock = new AnyRef 174 | var message: Option[String] = None 175 | val greeter = thread { 176 | lock.synchronized { 177 | while (message == None) lock.wait() 178 | log(message.get) 179 | } 180 | } 181 | lock.synchronized { 182 | message = Some("Hello!") 183 | lock.notify() 184 | } 185 | greeter.join() 186 | } 187 | 188 | 189 | object SynchronizedPool extends App { 190 | import scala.collection._ 191 | 192 | private val tasks = mutable.Queue[() => Unit]() 193 | 194 | object Worker extends Thread { 195 | setDaemon(true) 196 | def poll() = tasks.synchronized { 197 | while (tasks.isEmpty) tasks.wait() 198 | tasks.dequeue() 199 | } 200 | override def run() = while (true) { 201 | val task = poll() 202 | task() 203 | } 204 | } 205 | 206 | Worker.start() 207 | 208 | def asynchronous(body: =>Unit) = tasks.synchronized { 209 | tasks.enqueue(() => body) 210 | tasks.notify() 211 | } 212 | 213 | asynchronous { log("Hello ") } 214 | asynchronous { log("World!") } 215 | } 216 | 217 | 218 | object SynchronizedGracefulShutdown extends App { 219 | import scala.collection._ 220 | import scala.annotation.tailrec 221 | 222 | private val tasks = mutable.Queue[() => Unit]() 223 | 224 | object Worker extends Thread { 225 | var terminated = false 226 | def poll(): Option[() => Unit] = tasks.synchronized { 227 | while (tasks.isEmpty && !terminated) tasks.wait() 228 | if (!terminated) Some(tasks.dequeue()) else None 229 | } 230 | @tailrec override def run() = poll() match { 231 | case Some(task) => task(); run() 232 | case None => 233 | } 234 | def shutdown() = tasks.synchronized { 235 | terminated = true 236 | tasks.notify() 237 | } 238 | } 239 | 240 | Worker.start() 241 | 242 | def asynchronous(body: =>Unit) = tasks.synchronized { 243 | tasks.enqueue(() => body) 244 | tasks.notify() 245 | } 246 | 247 | asynchronous { log("Hello ") } 248 | asynchronous { log("World!") } 249 | 250 | Thread.sleep(1000) 251 | 252 | Worker.shutdown() 253 | } 254 | 255 | 256 | 257 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/ch2/Threads.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package ch2 3 | 4 | 5 | 6 | 7 | 8 | 9 | object ThreadsMain extends App { 10 | val name = Thread.currentThread.getName 11 | println(s"I am the thread $name") 12 | } 13 | 14 | 15 | object ThreadsStart extends App { 16 | class MyThread extends Thread { 17 | override def run(): Unit = { 18 | println(s"I am ${Thread.currentThread.getName}") 19 | } 20 | } 21 | 22 | val t = new MyThread() 23 | t.start() 24 | println(s"I am ${Thread.currentThread.getName}") 25 | } 26 | 27 | 28 | object ThreadsCreation extends App { 29 | 30 | class MyThread extends Thread { 31 | override def run(): Unit = { 32 | println("New thread running.") 33 | } 34 | } 35 | val t = new MyThread 36 | 37 | t.start() 38 | t.join() 39 | println("New thread joined.") 40 | 41 | } 42 | 43 | 44 | object ThreadsSleep extends App { 45 | 46 | val t = thread { 47 | Thread.sleep(1000) 48 | log("New thread running.") 49 | Thread.sleep(1000) 50 | log("Still running.") 51 | Thread.sleep(1000) 52 | log("Completed.") 53 | } 54 | t.join() 55 | log("New thread joined.") 56 | 57 | } 58 | 59 | 60 | object ThreadsNondeterminism extends App { 61 | 62 | val t = thread { log("New thread running.") } 63 | log("...") 64 | log("...") 65 | t.join() 66 | log("New thread joined.") 67 | 68 | } 69 | 70 | 71 | object ThreadsCommunicate extends App { 72 | var result: String = null 73 | val t = thread { result = "\nTitle\n" + "=" * 5 } 74 | t.join() 75 | log(result) 76 | } 77 | 78 | 79 | object ThreadsUnprotectedUid extends App { 80 | 81 | var uidCount = 0L 82 | 83 | def getUniqueId() = { 84 | val freshUid = uidCount + 1 85 | uidCount = freshUid 86 | freshUid 87 | } 88 | 89 | def printUniqueIds(n: Int): Unit = { 90 | val uids = for (i <- 0 until n) yield getUniqueId() 91 | log(s"Generated uids: $uids") 92 | } 93 | 94 | val t = thread { 95 | printUniqueIds(5) 96 | } 97 | printUniqueIds(5) 98 | t.join() 99 | 100 | } 101 | 102 | 103 | object ThreadSharedStateAccessReordering extends App { 104 | for (i <- 0 until 100000) { 105 | var a = false 106 | var b = false 107 | var x = -1 108 | var y = -1 109 | 110 | val t1 = thread { 111 | //Thread.sleep(2) 112 | a = true 113 | y = if (b) 0 else 1 114 | } 115 | val t2 = thread { 116 | //Thread.sleep(2) 117 | b = true 118 | x = if (a) 0 else 1 119 | } 120 | 121 | t1.join() 122 | t2.join() 123 | assert(!(x == 1 && y == 1), s"x = $x, y = $y") 124 | } 125 | } 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/ch2/Unsafe.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package ch2 3 | 4 | 5 | 6 | 7 | 8 | 9 | object UnsafeUid extends App { 10 | import scala.annotation.tailrec 11 | private val unsafe = scala.concurrent.util.Unsafe.instance 12 | private val uidCountOffset = unsafe.objectFieldOffset(UnsafeUid.getClass.getDeclaredField("uidCount")) 13 | @volatile var uidCount = 0L 14 | 15 | @tailrec def getUniqueId(): Long = { 16 | val oldUid = uidCount 17 | val newUid = uidCount + 1 18 | if (unsafe.compareAndSwapLong(UnsafeUid, uidCountOffset, oldUid, newUid)) newUid 19 | else getUniqueId() 20 | } 21 | 22 | def getUniqueIds(n: Int): Unit = { 23 | val uids = for (i <- 0 until n) yield getUniqueId() 24 | log(s"Generated uids: $uids") 25 | } 26 | 27 | val t = thread { 28 | getUniqueIds(5) 29 | } 30 | getUniqueIds(5) 31 | t.join() 32 | 33 | } 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/ch2/Volatile.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package ch2 3 | 4 | 5 | 6 | 7 | 8 | 9 | object Volatile extends App { 10 | case class Page(txt: String, var position: Int) 11 | val pages = for (i <- 1 to 5) yield 12 | new Page("Na" * (100 - 20 * i) + " Batman!", -1) 13 | @volatile var found = false 14 | for (p <- pages) yield thread { 15 | var i = 0 16 | while (i < p.txt.length && !found) 17 | if (p.txt(i) == '!') { 18 | p.position = i 19 | found = true 20 | } else i += 1 21 | } 22 | while (!found) {} 23 | log(s"results: ${pages.map(_.position)}") 24 | } 25 | 26 | 27 | object VolatileScan extends App { 28 | val document: Seq[String] = for (i <- 1 to 5) yield "lorem ipsum " * (1000 - 200 * i) + "Scala" 29 | var results = Array.fill(document.length)(-1) 30 | @volatile var found = false 31 | val threads = for (i <- 0 until document.length) yield thread { 32 | def scan(n: Int, words: Seq[String], query: String): Unit = 33 | if (words(n) == query) { 34 | results(i) = n 35 | found = true 36 | } else if (!found) scan(n + 1, words, query) 37 | scan(0, document(i).split(" "), "Scala") 38 | } 39 | for (t <- threads) t.join() 40 | log(s"Found: ${results.find(_ != -1)}") 41 | } 42 | 43 | 44 | object VolatileUnprotectedUid extends App { 45 | 46 | @volatile var uidCount = 0L 47 | 48 | def getUniqueId() = { 49 | val freshUid = uidCount + 1 50 | uidCount = freshUid 51 | freshUid 52 | } 53 | 54 | def printUniqueIds(n: Int): Unit = { 55 | val uids = for (i <- 0 until n) yield getUniqueId() 56 | log(s"Generated uids: $uids") 57 | } 58 | 59 | val t = thread { 60 | printUniqueIds(5) 61 | } 62 | printUniqueIds(5) 63 | t.join() 64 | 65 | } 66 | 67 | 68 | object VolatileSharedStateAccess extends App { 69 | for (i <- 0 until 10000) { 70 | @volatile var t1started = false 71 | @volatile var t2started = false 72 | var t1index = -1 73 | var t2index = -1 74 | 75 | val t1 = thread { 76 | Thread.sleep(1) 77 | t1started = true 78 | t2index = if (t2started) 0 else 1 79 | } 80 | val t2 = thread { 81 | Thread.sleep(1) 82 | t2started = true 83 | t1index = if (t1started) 0 else 1 84 | } 85 | 86 | t1.join() 87 | t2.join() 88 | assert(!(t1index == 1 && t2index == 1), s"t1 = $t1index, t2 = $t2index") 89 | } 90 | } 91 | 92 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/ch2/package.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | 3 | 4 | 5 | 6 | 7 | 8 | package object ch2 { 9 | 10 | def thread(body: =>Unit): Thread = { 11 | val t = new Thread { 12 | override def run() = body 13 | } 14 | t.start() 15 | t 16 | } 17 | 18 | } 19 | 20 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/ch3/Atomic.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package ch3 3 | 4 | 5 | 6 | 7 | 8 | 9 | object AtomicUid extends App { 10 | import java.util.concurrent.atomic._ 11 | private val uid = new AtomicLong(0L) 12 | 13 | def getUniqueId(): Long = uid.incrementAndGet() 14 | 15 | execute { 16 | log(s"Got a unique id asynchronously: ${getUniqueId()}") 17 | } 18 | 19 | log(s"Got a unique id: ${getUniqueId()}") 20 | } 21 | 22 | 23 | object AtomicUidCAS extends App { 24 | import java.util.concurrent.atomic._ 25 | import scala.annotation.tailrec 26 | private val uid = new AtomicLong(0L) 27 | 28 | @tailrec def getUniqueId(): Long = { 29 | val oldUid = uid.get 30 | val newUid = oldUid + 1 31 | if (uid.compareAndSet(oldUid, newUid)) newUid 32 | else getUniqueId() 33 | } 34 | 35 | execute { 36 | log(s"Got a unique id asynchronously: $getUniqueId") 37 | } 38 | 39 | log(s"Got a unique id: $getUniqueId") 40 | } 41 | 42 | 43 | object AtomicLock extends App { 44 | import java.util.concurrent.atomic._ 45 | private val lock = new AtomicBoolean(false) 46 | def mySynchronized(body: =>Unit): Unit = { 47 | while (!lock.compareAndSet(false, true)) {} 48 | try body 49 | finally lock.set(false) 50 | } 51 | 52 | var count = 0 53 | for (i <- 0 until 10) execute { 54 | mySynchronized { count += 1 } 55 | } 56 | Thread.sleep(1000) 57 | log(s"Count is: $count") 58 | } 59 | 60 | 61 | object AtomicStack { 62 | import java.util.concurrent.atomic._ 63 | import scala.concurrent._ 64 | import scala.annotation.tailrec 65 | 66 | trait Stack 67 | case class Node(head: Int, tail: Stack) extends Stack 68 | case object Bottom extends Stack 69 | 70 | private val stack = new AtomicReference[Stack](Bottom) 71 | @tailrec def push(x: Int) { 72 | val oldTop = stack.get() 73 | val newTop = Node(x, oldTop) 74 | if (!stack.compareAndSet(oldTop, newTop)) push(x) 75 | } 76 | @tailrec def pop(): Option[Int] = { 77 | stack.get() match { 78 | case Bottom => None 79 | case oldTop @ Node(head, newTop) => 80 | if (stack.compareAndSet(oldTop, newTop)) Some(head) 81 | else pop() 82 | } 83 | } 84 | 85 | def main(args: Array[String]) { 86 | execute { 87 | @tailrec def poll() { 88 | pop() match { 89 | case Some(-1) => 90 | log("Got -1. Done!") 91 | case Some(x) => 92 | log(s"Got $x") 93 | poll() 94 | case None => 95 | poll() 96 | } 97 | } 98 | poll() 99 | } 100 | 101 | push(1) 102 | push(2) 103 | push(3) 104 | Thread.sleep(100) 105 | push(-1) 106 | } 107 | 108 | } 109 | 110 | 111 | object AtomicArrays extends App { 112 | import java.util.concurrent.atomic._ 113 | import scala.concurrent._ 114 | 115 | private val counts = new AtomicIntegerArray(4) 116 | def lowerBound(): Int = { 117 | var cnt = 0 118 | for (i <- 0 until counts.length) cnt += counts.get(i) 119 | cnt 120 | } 121 | 122 | for (i <- 0 until counts.length) execute { 123 | for (_ <- 0 until 200) counts.incrementAndGet(i) 124 | } 125 | 126 | log(s"Count lower bound: ${lowerBound()}") 127 | log(s"Count lower bound: ${lowerBound()}") 128 | log(s"Count lower bound: ${lowerBound()}") 129 | } 130 | 131 | 132 | 133 | 134 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/ch3/Collections.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package ch3 3 | 4 | 5 | 6 | 7 | 8 | 9 | object CollectionsBad extends App { 10 | import scala.collection._ 11 | 12 | val buffer = mutable.ArrayBuffer[Int]() 13 | 14 | def add(numbers: Seq[Int]) = execute { 15 | buffer ++= numbers 16 | log(s"buffer = $buffer") 17 | } 18 | 19 | add(0 until 10) 20 | add(10 until 20) 21 | } 22 | 23 | 24 | /* 25 | // This example is not in the book. 26 | object CollectionsSynchronized extends App { 27 | import scala.collection._ 28 | 29 | val buffer = new mutable.BufferProxy[Int] with mutable.SynchronizedBuffer[Int] { 30 | val self = mutable.ArrayBuffer[Int]() 31 | } 32 | 33 | execute { 34 | buffer ++= (0 until 10) 35 | log(s"buffer = $buffer") 36 | } 37 | 38 | execute { 39 | buffer ++= (10 until 20) 40 | log(s"buffer = $buffer") 41 | } 42 | 43 | } 44 | */ 45 | 46 | 47 | object MiscSyncVars extends App { 48 | import scala.concurrent._ 49 | val sv = new SyncVar[String] 50 | 51 | execute { 52 | Thread.sleep(500) 53 | log("sending a message") 54 | sv.put("This is secret.") 55 | } 56 | 57 | log(s"get = ${sv.get}") 58 | log(s"take = ${sv.take()}") 59 | 60 | execute { 61 | Thread.sleep(500) 62 | log("sending another message") 63 | sv.put("Secrets should not be logged!") 64 | } 65 | 66 | log(s"take = ${sv.take()}") 67 | log(s"take = ${sv.take(timeout = 1000)}") 68 | } 69 | 70 | 71 | object MiscDynamicVars extends App { 72 | import scala.util.DynamicVariable 73 | 74 | val dynlog = new DynamicVariable[String => Unit](log) 75 | def secretLog(msg: String) = println(s"(unknown thread): $msg") 76 | 77 | execute { 78 | dynlog.value("Starting asynchronous execution.") 79 | dynlog.withValue(secretLog) { 80 | dynlog.value("Nobody knows who I am.") 81 | } 82 | dynlog.value("Ending asynchronous execution.") 83 | } 84 | 85 | dynlog.value("is calling the log method!") 86 | } 87 | 88 | 89 | object CollectionsIterators extends App { 90 | import java.util.concurrent._ 91 | 92 | val queue = new LinkedBlockingQueue[String] 93 | for (i <- 1 to 5500) queue.offer(i.toString) 94 | execute { 95 | val it = queue.iterator 96 | while (it.hasNext) log(it.next()) 97 | } 98 | for (i <- 1 to 5500) queue.poll() 99 | } 100 | 101 | 102 | object CollectionsConcurrentMap extends App { 103 | import java.util.concurrent.ConcurrentHashMap 104 | import scala.collection._ 105 | import scala.collection.convert.decorateAsScala._ 106 | import scala.annotation.tailrec 107 | 108 | val emails = new ConcurrentHashMap[String, List[String]]().asScala 109 | 110 | execute { 111 | emails("James Gosling") = List("james@javalove.com") 112 | log(s"emails = $emails") 113 | } 114 | 115 | execute { 116 | emails.putIfAbsent("Alexey Pajitnov", List("alexey@tetris.com")) 117 | log(s"emails = $emails") 118 | } 119 | 120 | execute { 121 | emails.putIfAbsent("Alexey Pajitnov", List("alexey@welltris.com")) 122 | log(s"emails = $emails") 123 | } 124 | 125 | } 126 | 127 | 128 | object CollectionsConcurrentMapIncremental extends App { 129 | import java.util.concurrent.ConcurrentHashMap 130 | import scala.collection._ 131 | import scala.collection.convert.decorateAsScala._ 132 | import scala.annotation.tailrec 133 | 134 | val emails = new ConcurrentHashMap[String, List[String]]().asScala 135 | 136 | @tailrec def addEmail(name: String, address: String) { 137 | emails.get(name) match { 138 | case Some(existing) => 139 | if (!emails.replace(name, existing, address :: existing)) addEmail(name, address) 140 | case None => 141 | if (emails.putIfAbsent(name, address :: Nil) != None) addEmail(name, address) 142 | } 143 | } 144 | 145 | execute { 146 | addEmail("Yukihiro Matsumoto", "ym@ruby.com") 147 | log(s"emails = $emails") 148 | } 149 | 150 | execute { 151 | addEmail("Yukihiro Matsumoto", "ym@ruby.io") 152 | log(s"emails = $emails") 153 | } 154 | 155 | } 156 | 157 | 158 | object CollectionsConcurrentMapBulk extends App { 159 | import scala.collection._ 160 | import scala.collection.convert.decorateAsScala._ 161 | import java.util.concurrent.ConcurrentHashMap 162 | 163 | val names = new ConcurrentHashMap[String, Int]().asScala 164 | names("Johnny") = 0 165 | names("Jane") = 0 166 | names("Jack") = 0 167 | 168 | execute { 169 | for (n <- 0 until 10) names(s"John $n") = n 170 | } 171 | 172 | execute { 173 | for (n <- names) log(s"name: $n") 174 | } 175 | 176 | } 177 | 178 | 179 | object CollectionsTrieMapBulk extends App { 180 | import scala.collection._ 181 | 182 | val names = new concurrent.TrieMap[String, Int] 183 | names("Janice") = 0 184 | names("Jackie") = 0 185 | names("Jill") = 0 186 | 187 | execute { 188 | for (n <- 10 until 100) names(s"John $n") = n 189 | } 190 | 191 | execute { 192 | log("snapshot time!") 193 | for (n <- names.map(_._1).toSeq.sorted) log(s"name: $n") 194 | } 195 | 196 | } 197 | 198 | 199 | 200 | 201 | 202 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/ch3/Executors.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package ch3 3 | 4 | 5 | 6 | 7 | 8 | 9 | object ExecutorsCreate extends App { 10 | import scala.concurrent._ 11 | val executor = new java.util.concurrent.ForkJoinPool 12 | executor.execute(new Runnable { 13 | def run() = log("This task is run asynchronously.") 14 | }) 15 | 16 | Thread.sleep(500) 17 | } 18 | 19 | 20 | object ExecutionContextGlobal extends App { 21 | import scala.concurrent._ 22 | val ectx = ExecutionContext.global 23 | ectx.execute(new Runnable { 24 | def run() = log("Running on the execution context.") 25 | }) 26 | } 27 | 28 | 29 | object ExecutionContextCreate extends App { 30 | import scala.concurrent._ 31 | val ectx = ExecutionContext.fromExecutorService(new forkjoin.ForkJoinPool) 32 | ectx.execute(new Runnable { 33 | def run() = log("Running on the execution context again.") 34 | }) 35 | } 36 | 37 | 38 | object ExecutionContextSleep extends App { 39 | import scala.concurrent._ 40 | for (i <- 0 until 32) execute { 41 | Thread.sleep(2000) 42 | log(s"Task $i completed.") 43 | } 44 | Thread.sleep(10000) 45 | } 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/ch3/FileSystem.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package ch3 3 | 4 | 5 | 6 | import java.io._ 7 | import java.util.concurrent._ 8 | import java.util.concurrent.atomic._ 9 | import scala.annotation.tailrec 10 | import scala.collection._ 11 | import scala.collection.convert.decorateAsScala._ 12 | import org.apache.commons.io.FileUtils 13 | 14 | 15 | 16 | object FileSystemTest extends App { 17 | val fileSystem = new FileSystem(".") 18 | 19 | fileSystem.logMessage("Testing log!") 20 | 21 | fileSystem.deleteFile("test.txt") 22 | 23 | fileSystem.copyFile("build.sbt", "build.sbt.backup") 24 | 25 | val rootFiles = fileSystem.filesInDir("") 26 | log("All files in the root dir: " + rootFiles.mkString(", ")) 27 | } 28 | 29 | 30 | class FileSystem(val root: String) { 31 | 32 | private val messages = new LinkedBlockingQueue[String] 33 | 34 | val logger = new Thread { 35 | setDaemon(true) 36 | override def run() { 37 | while (true) { 38 | val msg = messages.take() 39 | log(msg) 40 | } 41 | } 42 | } 43 | 44 | logger.start() 45 | 46 | def logMessage(msg: String): Unit = messages.add(msg) 47 | 48 | sealed trait State 49 | 50 | class Idle extends State 51 | 52 | class Creating extends State 53 | 54 | class Copying(val n: Int) extends State 55 | 56 | class Deleting extends State 57 | 58 | class Entry(val isDir: Boolean) { 59 | val state = new AtomicReference[State](new Idle) 60 | } 61 | 62 | val files: concurrent.Map[String, Entry] = 63 | //new ConcurrentHashMap().asScala 64 | new concurrent.TrieMap() 65 | 66 | for (file <- FileUtils.iterateFiles(new File(root), null, false).asScala) { 67 | files.put(file.getName, new Entry(false)) 68 | } 69 | 70 | @tailrec private def prepareForDelete(entry: Entry): Boolean = { 71 | val s0 = entry.state.get 72 | s0 match { 73 | case i: Idle => 74 | if (entry.state.compareAndSet(s0, new Deleting)) true 75 | else prepareForDelete(entry) 76 | case c: Creating => 77 | logMessage("File currently being created, cannot delete.") 78 | false 79 | case c: Copying => 80 | logMessage("File currently being copied, cannot delete.") 81 | false 82 | case d: Deleting => 83 | false 84 | } 85 | } 86 | 87 | def deleteFile(filename: String): Unit = { 88 | files.get(filename) match { 89 | case None => 90 | logMessage(s"Cannot delete - path '$filename' does not exist!") 91 | case Some(entry) if entry.isDir => 92 | logMessage(s"Cannot delete - path '$filename' is a directory!") 93 | case Some(entry) => 94 | execute { 95 | if (prepareForDelete(entry)) { 96 | if (FileUtils.deleteQuietly(new File(filename))) 97 | files.remove(filename) 98 | } 99 | } 100 | } 101 | } 102 | 103 | @tailrec private def acquire(entry: Entry): Boolean = { 104 | val s0 = entry.state.get 105 | s0 match { 106 | case _: Creating | _: Deleting => 107 | logMessage("File inaccessible, cannot copy.") 108 | false 109 | case i: Idle => 110 | if (entry.state.compareAndSet(s0, new Copying(1))) true 111 | else acquire(entry) 112 | case c: Copying => 113 | if (entry.state.compareAndSet(s0, new Copying(c.n + 1))) true 114 | else acquire(entry) 115 | } 116 | } 117 | 118 | @tailrec private def release(entry: Entry): Unit = { 119 | val s0 = entry.state.get 120 | s0 match { 121 | case i: Idle => 122 | sys.error("Error - released more times than acquired.") 123 | case c: Creating => 124 | if (!entry.state.compareAndSet(s0, new Idle)) release(entry) 125 | case c: Copying if c.n <= 0 => 126 | sys.error("Error - cannot have 0 or less copies in progress!") 127 | case c: Copying => 128 | val newState = if (c.n == 1) new Idle else new Copying(c.n - 1) 129 | if (!entry.state.compareAndSet(s0, newState)) release(entry) 130 | case d: Deleting => 131 | sys.error("Error - releasing a file that is being deleted!") 132 | } 133 | } 134 | 135 | def copyFile(src: String, dest: String): Unit = { 136 | files.get(src) match { 137 | case None => 138 | logMessage(s"File '$src' does not exist.") 139 | case Some(srcEntry) if srcEntry.isDir => 140 | sys.error(s"Path '$src' is a directory!") 141 | case Some(srcEntry) => 142 | execute { 143 | if (acquire(srcEntry)) try { 144 | val destEntry = new Entry(false) 145 | destEntry.state.set(new Creating) 146 | if (files.putIfAbsent(dest, destEntry) == None) try { 147 | FileUtils.copyFile(new File(src), new File(dest)) 148 | } finally release(destEntry) 149 | } finally release(srcEntry) 150 | } 151 | } 152 | } 153 | 154 | def filesInDir(dir: String): Iterable[String] = { 155 | // trie map snapshots 156 | for ((name, state) <- files; if name.startsWith(dir)) yield name 157 | } 158 | 159 | } 160 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/ch3/LazyVals.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package ch3 3 | 4 | 5 | 6 | 7 | 8 | 9 | object LazyValsCreate extends App { 10 | import scala.concurrent._ 11 | 12 | lazy val obj = new AnyRef 13 | lazy val nondeterministic = s"made by ${Thread.currentThread.getName}" 14 | 15 | execute { 16 | log(s"Execution context thread sees object = $obj") 17 | log(s"Execution context thread sees nondeterministic = $nondeterministic") 18 | } 19 | 20 | log(s"Main thread sees object = $obj") 21 | log(s"Main thread sees nondeterministic = $nondeterministic") 22 | } 23 | 24 | 25 | object LazyValsObject extends App { 26 | object Lazy { 27 | log("Running Lazy constructor.") 28 | } 29 | 30 | log("Main thread is about to reference Lazy.") 31 | Lazy 32 | log("Main thread completed.") 33 | } 34 | 35 | 36 | object LazyValsUnderTheHood extends App { 37 | @volatile private var _bitmap = false 38 | private var _obj: AnyRef = _ 39 | def obj = if (_bitmap) _obj else this.synchronized { 40 | if (!_bitmap) { 41 | _obj = new AnyRef 42 | _bitmap = true 43 | } 44 | _obj 45 | } 46 | 47 | log(s"$obj"); log(s"$obj") 48 | } 49 | 50 | 51 | object LazyValsInspectMainThread extends App { 52 | val mainThread = Thread.currentThread 53 | 54 | lazy val x = { 55 | log(s"running Lazy ctor") 56 | Thread.sleep(1000) 57 | log(s"main thread state - ${mainThread.getState}") 58 | } 59 | 60 | execute { x } 61 | 62 | log("started asynchronous thread") 63 | Thread.sleep(200) 64 | log("log about to access x") 65 | x 66 | } 67 | 68 | 69 | object LazyValsDeadlock extends App { 70 | object A { 71 | lazy val x: Int = B.y 72 | } 73 | object B { 74 | lazy val y: Int = A.x 75 | } 76 | 77 | execute { B.y } 78 | 79 | A.x 80 | } 81 | 82 | 83 | object LazyValsAndSynchronized extends App { 84 | lazy val terminatedThread = { 85 | val t = ch2.thread { 86 | LazyValsAndSynchronized.synchronized {} 87 | } 88 | t.join() 89 | t.getState 90 | } 91 | 92 | terminatedThread 93 | } 94 | 95 | 96 | object LazyValsAndBlocking extends App { 97 | lazy val x: Int = { 98 | val t = ch2.thread { 99 | println(s"Initializing $x.") 100 | } 101 | t.join() 102 | 1 103 | } 104 | x 105 | } 106 | 107 | 108 | object LazyValsAndMonitors extends App { 109 | lazy val x = 1 110 | this.synchronized { 111 | val t = ch2.thread { x } 112 | t.join() 113 | } 114 | } 115 | 116 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/ch3/LockFreePool.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package ch3 3 | 4 | 5 | 6 | import java.util.concurrent.ConcurrentHashMap 7 | import java.util.concurrent.atomic.AtomicReference 8 | import scala.annotation.tailrec 9 | 10 | 11 | 12 | object LockFreePool { 13 | class Pool[T] { 14 | val parallelism = Runtime.getRuntime.availableProcessors * 32 15 | val buckets = new Array[AtomicReference[(List[T], Long)]](parallelism) 16 | for (i <- 0 until buckets.length) 17 | buckets(i) = new AtomicReference((Nil, 0L)) 18 | 19 | def add(x: T): Unit = { 20 | val i = (Thread.currentThread.getId * x.## % buckets.length).toInt 21 | @tailrec def retry() { 22 | val bucket = buckets(i) 23 | val v = bucket.get 24 | val (lst, stamp) = v 25 | val nlst = x :: lst 26 | val nstamp = stamp + 1 27 | val nv = (nlst, nstamp) 28 | if (!bucket.compareAndSet(v, nv)) retry() 29 | } 30 | retry() 31 | } 32 | 33 | def remove(): Option[T] = { 34 | val start = (Thread.currentThread.getId % buckets.length).toInt 35 | @tailrec def scan(witness: Long): Option[T] = { 36 | var i = start 37 | var sum = 0L 38 | do { 39 | val bucket = buckets(i) 40 | 41 | @tailrec def retry(): Option[T] = { 42 | bucket.get match { 43 | case (Nil, stamp) => 44 | sum += stamp 45 | None 46 | case v @ (lst, stamp) => 47 | val nv = (lst.tail, stamp + 1) 48 | if (bucket.compareAndSet(v, nv)) Some(lst.head) 49 | else retry() 50 | } 51 | } 52 | retry() match { 53 | case Some(v) => return Some(v) 54 | case None => 55 | } 56 | 57 | i = (i + 1) % buckets.length 58 | } while (i != start) 59 | if (sum == witness) None 60 | else scan(sum) 61 | } 62 | scan(-1L) 63 | } 64 | } 65 | 66 | def main(args: Array[String]) { 67 | val check = new ConcurrentHashMap[Int, Unit]() 68 | val pool = new Pool[Int] 69 | val p = 8 70 | val num = 1000000 71 | val inserters = for (i <- 0 until p) yield ch2.thread { 72 | for (j <- 0 until num) pool.add(i * num + j) 73 | } 74 | inserters.foreach(_.join()) 75 | val removers = for (i <- 0 until p) yield ch2.thread { 76 | for (j <- 0 until num) { 77 | pool.remove() match { 78 | case Some(v) => check.put(v, ()) 79 | case None => sys.error("Should be non-empty.") 80 | } 81 | } 82 | } 83 | removers.foreach(_.join()) 84 | for (i <- 0 until (num * p)) assert(check.containsKey(i)) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/ch3/Processes.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package ch3 3 | 4 | 5 | 6 | 7 | 8 | 9 | object ProcessRun extends App { 10 | import scala.sys.process._ 11 | 12 | val command = "ls" 13 | val exitcode = command.! 14 | 15 | log(s"command exited with status $exitcode") 16 | 17 | } 18 | 19 | 20 | object ProcessLineCount extends App { 21 | import scala.sys.process._ 22 | 23 | def lineCount(filename: String): Int = { 24 | val output = s"wc $filename".!! 25 | 26 | output.trim.split(" ").head.toInt 27 | } 28 | 29 | val lc = lineCount("build.sbt") 30 | log(s"File build.sbt has $lc lines.") 31 | } 32 | 33 | 34 | object ProcessAsync extends App { 35 | import scala.sys.process._ 36 | val lsProcess = "ls -R /".run() 37 | Thread.sleep(1000) 38 | log("Timeout - killing ls!") 39 | lsProcess.destroy() 40 | } 41 | 42 | 43 | object ProcessList extends App { 44 | import scala.sys.process._ 45 | 46 | def processes(): Stream[(Int, String)] = { 47 | val proclines = "ps -A".lineStream_! 48 | proclines.tail map { line => 49 | val parts = line.trim.split(" ") 50 | (parts.head.toInt, parts.last) 51 | } 52 | } 53 | 54 | val currprocs = processes() 55 | val jvms = currprocs.filter(_._2.contains("java")) 56 | log(s"listing currently running JVM instances...") 57 | for (jvm <- jvms) { 58 | log(s"${jvm._1} ${jvm._2}") 59 | } 60 | 61 | } 62 | 63 | 64 | object ProcessFiles extends App { 65 | import scala.sys.process._ 66 | 67 | def files(pattern: String): Stream[String] = s"find /home/ -name $pattern".lineStream_! 68 | 69 | for (file <- files("scala")) log(s"found - $file") 70 | } 71 | 72 | 73 | object ProcessPipelining extends App { 74 | import scala.sys.process._ 75 | 76 | case class ProcessInfo(pid: Int, name: String) 77 | 78 | def processes(pattern: String): Stream[ProcessInfo] = { 79 | val proclines = "ps -A" #| s"grep $pattern" lineStream_!; 80 | proclines map { line => 81 | val parts = line.trim.split(" ") 82 | ProcessInfo(parts.head.toInt, parts.last) 83 | } 84 | } 85 | 86 | val jvms = processes("java") 87 | for (jvm <- jvms) { 88 | log(s"Found a running `java` process, pid = ${jvm.pid}") 89 | } 90 | 91 | } 92 | 93 | 94 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/ch3/package.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | 3 | 4 | 5 | import scala.concurrent._ 6 | 7 | 8 | 9 | package object ch3 { 10 | 11 | def execute(body: =>Unit) = ExecutionContext.global.execute(new Runnable { 12 | def run() = body 13 | }) 14 | 15 | } 16 | 17 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/ch4/Alternative.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package ch4 3 | 4 | 5 | 6 | 7 | 8 | // // only enable with Scala 2.10.4 9 | // object Finagle extends App { 10 | // import com.twitter.util.{Future, Promise} 11 | 12 | // val tombola = Future { 13 | // scala.util.Random.shuffle((0 until 10000).toVector) 14 | // } 15 | 16 | // tombola onSuccess { numbers => 17 | // log(s"And the winner is: ${numbers.head}") 18 | // } 19 | 20 | // tombola onSuccess { numbers => 21 | // log(s"Once, more, the winner is: ${numbers.head}") 22 | // } 23 | 24 | // } 25 | 26 | 27 | object Scalaz extends App { 28 | import scalaz.concurrent._ 29 | 30 | val tombola = Future { 31 | scala.util.Random.shuffle((0 until 10000).toVector) 32 | } 33 | 34 | tombola.runAsync { numbers => 35 | log(s"And the winner is: ${numbers.head}") 36 | } 37 | 38 | tombola.runAsync { numbers => 39 | log(s"... ahem, winner is: ${numbers.head}") 40 | } 41 | 42 | } 43 | 44 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/ch4/Async.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package ch4 3 | 4 | 5 | 6 | 7 | 8 | 9 | object AsyncBasic extends App { 10 | import scala.concurrent._ 11 | import ExecutionContext.Implicits.global 12 | import scala.async.Async.{async, await} 13 | 14 | val workerName: Future[String] = async { 15 | Thread.currentThread.getName 16 | } 17 | 18 | workerName foreach { 19 | name => log(s"Future completed by worker $name") 20 | } 21 | 22 | } 23 | 24 | 25 | object AsyncAwait extends App { 26 | import scala.concurrent._ 27 | import ExecutionContext.Implicits.global 28 | import scala.async.Async.{async, await} 29 | import scala.io.Source 30 | 31 | val timetableFuture: Future[String] = async { 32 | val utc: Future[String] = async { Source.fromURL("http://www.timeapi.org/utc/now").mkString } 33 | val pdt: Future[String] = async { Source.fromURL("http://www.timeapi.org/pdt/now").mkString } 34 | val wet: Future[String] = async { Source.fromURL("http://www.timeapi.org/west/now").mkString } 35 | s"""Timetable 36 | Universal Time ${await { utc } } 37 | Pacific Daylight Time ${await { pdt } } 38 | Western European Summer Time ${await { wet } } 39 | """ 40 | } 41 | 42 | timetableFuture foreach { 43 | timetable => log(timetable) 44 | } 45 | 46 | } 47 | 48 | 49 | object AsyncWhile extends App { 50 | import scala.concurrent._ 51 | import ExecutionContext.Implicits.global 52 | import scala.async.Async.{async, await} 53 | 54 | def delay(nSeconds: Int) = async { 55 | blocking { 56 | Thread.sleep(nSeconds * 1000) 57 | } 58 | } 59 | 60 | def simpleCount(): Future[Unit] = async { 61 | log("T-minus 2 seconds") 62 | await { delay(1) } 63 | log("T-minus 1 second") 64 | await { delay(1) } 65 | log("done!") 66 | } 67 | 68 | simpleCount() 69 | 70 | Thread.sleep(3000) 71 | 72 | def countdown(nSeconds: Int)(count: Int => Unit): Future[Unit] = async { 73 | var i = nSeconds 74 | while (i > 0) { 75 | count(i) 76 | await { delay(1) } 77 | i -= 1 78 | } 79 | } 80 | 81 | countdown(10) { n => 82 | log(s"T-minus $n seconds") 83 | } foreach { 84 | _ => log(s"This program is over!") 85 | } 86 | 87 | } 88 | 89 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/ch4/Blocking.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package ch4 3 | 4 | 5 | 6 | 7 | 8 | 9 | object BlockingAwait extends App { 10 | import scala.concurrent._ 11 | import ExecutionContext.Implicits.global 12 | import scala.concurrent.duration._ 13 | import scala.io.Source 14 | 15 | val urlSpecSizeFuture = Future { Source.fromURL("http://www.w3.org/Addressing/URL/url-spec.txt").size } 16 | val urlSpecSize = Await.result(urlSpecSizeFuture, 10.seconds) 17 | 18 | log(s"url spec contains $urlSpecSize characters") 19 | 20 | } 21 | 22 | 23 | object BlockingSleepBad extends App { 24 | import scala.concurrent._ 25 | import ExecutionContext.Implicits.global 26 | import scala.concurrent.duration._ 27 | 28 | val startTime = System.nanoTime 29 | 30 | val futures = for (_ <- 0 until 16) yield Future { 31 | Thread.sleep(1000) 32 | } 33 | 34 | for (f <- futures) Await.ready(f, Duration.Inf) 35 | 36 | val endTime = System.nanoTime 37 | 38 | log(s"Total execution time of the program = ${(endTime - startTime) / 1000000} ms") 39 | log(s"Note: there are ${Runtime.getRuntime.availableProcessors} CPUs on this machine") 40 | 41 | } 42 | 43 | 44 | object BlockingSleepOk extends App { 45 | import scala.concurrent._ 46 | import ExecutionContext.Implicits.global 47 | import scala.concurrent.duration._ 48 | 49 | val startTime = System.nanoTime 50 | 51 | val futures = for (_ <- 0 until 16) yield Future { 52 | blocking { 53 | Thread.sleep(1000) 54 | } 55 | } 56 | 57 | for (f <- futures) Await.ready(f, Duration.Inf) 58 | 59 | val endTime = System.nanoTime 60 | 61 | log(s"Total execution time of the program = ${(endTime - startTime) / 1000000} ms") 62 | 63 | } 64 | 65 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/ch4/Promises.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package ch4 3 | 4 | 5 | 6 | 7 | 8 | 9 | object PromisesCreate extends App { 10 | import scala.concurrent._ 11 | import ExecutionContext.Implicits.global 12 | 13 | val p = Promise[String] 14 | val q = Promise[String] 15 | 16 | p.future foreach { 17 | case text => log(s"Promise p succeeded with '$text'") 18 | } 19 | 20 | p success "kept" 21 | 22 | val secondAttempt = p trySuccess "kept again" 23 | 24 | log(s"Second attempt to complete the same promise went well? $secondAttempt") 25 | 26 | q failure new Exception("not kept") 27 | 28 | q.future.failed foreach { 29 | case t => log(s"Promise q failed with $t") 30 | } 31 | 32 | } 33 | 34 | 35 | object PromisesCustomAsync extends App { 36 | import scala.concurrent._ 37 | import ExecutionContext.Implicits.global 38 | import scala.util.control.NonFatal 39 | 40 | def myFuture[T](body: =>T): Future[T] = { 41 | val p = Promise[T] 42 | 43 | global.execute(new Runnable { 44 | def run() = try { 45 | val result = body 46 | p success result 47 | } catch { 48 | case NonFatal(e) => 49 | p failure e 50 | } 51 | }) 52 | 53 | p.future 54 | } 55 | 56 | val future = myFuture { 57 | "naaa" + "na" * 8 + " Katamari Damacy!" 58 | } 59 | 60 | future foreach { 61 | case text => log(text) 62 | } 63 | 64 | } 65 | 66 | 67 | object PromisesAndCallbacks extends App { 68 | import scala.concurrent._ 69 | import ExecutionContext.Implicits.global 70 | import org.apache.commons.io.monitor._ 71 | import java.io.File 72 | 73 | def fileCreated(directory: String): Future[String] = { 74 | val p = Promise[String] 75 | 76 | val fileMonitor = new FileAlterationMonitor(1000) 77 | val observer = new FileAlterationObserver(directory) 78 | val listener = new FileAlterationListenerAdaptor { 79 | override def onFileCreate(file: File) { 80 | try p.trySuccess(file.getName) 81 | finally fileMonitor.stop() 82 | } 83 | } 84 | observer.addListener(listener) 85 | fileMonitor.addObserver(observer) 86 | fileMonitor.start() 87 | 88 | p.future 89 | } 90 | 91 | fileCreated(".") foreach { 92 | case filename => log(s"Detected new file '$filename'") 93 | } 94 | 95 | } 96 | 97 | 98 | object PromisesAndCustomOperations extends App { 99 | import scala.concurrent._ 100 | import ExecutionContext.Implicits.global 101 | 102 | implicit class FutureOps[T](val self: Future[T]) { 103 | def or(that: Future[T]): Future[T] = { 104 | val p = Promise[T] 105 | self onComplete { case x => p tryComplete x } 106 | that onComplete { case y => p tryComplete y } 107 | p.future 108 | } 109 | } 110 | 111 | val f = Future { "now" } or Future { "later" } 112 | 113 | f foreach { 114 | case when => log(s"The future is $when") 115 | } 116 | 117 | } 118 | 119 | 120 | object PromisesAndTimers extends App { 121 | import java.util._ 122 | import scala.concurrent._ 123 | import ExecutionContext.Implicits.global 124 | import PromisesAndCustomOperations._ 125 | 126 | private val timer = new Timer(true) 127 | 128 | def timeout(millis: Long): Future[Unit] = { 129 | val p = Promise[Unit] 130 | timer.schedule(new TimerTask { 131 | def run() = p.success(()) 132 | }, millis) 133 | p.future 134 | } 135 | 136 | val f = timeout(1000).map(_ => "timeout!") or Future { 137 | Thread.sleep(999) 138 | "work completed!" 139 | } 140 | 141 | f foreach { 142 | case text => log(text) 143 | } 144 | 145 | } 146 | 147 | 148 | object PromisesCancellation extends App { 149 | import scala.concurrent._ 150 | import ExecutionContext.Implicits.global 151 | 152 | def cancellable[T](b: Future[Unit] => T): (Promise[Unit], Future[T]) = { 153 | val p = Promise[Unit] 154 | val f = Future { 155 | val r = b(p.future) 156 | if (!p.tryFailure(new Exception)) 157 | throw new CancellationException 158 | r 159 | } 160 | (p, f) 161 | } 162 | 163 | val (cancel, value) = cancellable { cancel => 164 | var i = 0 165 | while (i < 5) { 166 | if (cancel.isCompleted) throw new CancellationException 167 | Thread.sleep(500) 168 | log(s"$i: working") 169 | i += 1 170 | } 171 | "resulting value" 172 | } 173 | 174 | Thread.sleep(1500) 175 | 176 | cancel.trySuccess(()) 177 | 178 | log("computation cancelled!") 179 | } 180 | 181 | 182 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/ch5/Blitz.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package ch5 3 | 4 | 5 | 6 | import scala.collection._ 7 | import scala.collection.par._ 8 | import scala.collection.par.Scheduler.Implicits.global 9 | 10 | 11 | 12 | object BlitzComparison extends App { 13 | val array = (0 until 100000).toArray 14 | @volatile var x = 0 15 | 16 | val seqtime = warmedTimed(1000) { 17 | array.reduce(_ + _) 18 | } 19 | val partime = warmedTimed(1000) { 20 | array.par.reduce(_ + _) 21 | } 22 | val blitztime = warmedTimed(1000) { 23 | x = array.toPar.reduce(_ + _) 24 | } 25 | 26 | log(s"sequential time - $seqtime") 27 | log(s"parallel time - $partime") 28 | log(s"ScalaBlitz time - $blitztime") 29 | } 30 | 31 | 32 | object BlitzHierarchy extends App { 33 | val array = (0 until 100000).toArray 34 | val range = 0 until 100000 35 | 36 | def sum(xs: Zippable[Int]): Int = { 37 | xs.reduce(_ + _) 38 | } 39 | 40 | println(sum(array.toPar)) 41 | 42 | println(sum(range.toPar)) 43 | 44 | } 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/ch5/Concurrent.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package ch5 3 | 4 | 5 | 6 | 7 | 8 | 9 | object ConcurrentWrong extends App { 10 | import scala.collection._ 11 | import scala.concurrent.ExecutionContext.Implicits.global 12 | import ParHtmlSpecSearch.getHtmlSpec 13 | import ch4.FuturesCallbacks.getUrlSpec 14 | 15 | def intersection(a: GenSet[String], b: GenSet[String]): GenSet[String] = { 16 | val result = new mutable.HashSet[String] 17 | for (x <- a.par) if (b contains x) result.add(x) 18 | result 19 | } 20 | 21 | val ifut = for { 22 | htmlSpec <- getHtmlSpec() 23 | urlSpec <- getUrlSpec() 24 | } yield { 25 | val htmlWords = htmlSpec.mkString.split("\\s+").toSet 26 | val urlWords = urlSpec.mkString.split("\\s+").toSet 27 | intersection(htmlWords, urlWords) 28 | } 29 | 30 | ifut onComplete { 31 | case t => log(s"Result: $t") 32 | } 33 | } 34 | 35 | 36 | object ConcurrentCollections extends App { 37 | import java.util.concurrent.ConcurrentSkipListSet 38 | import scala.collection._ 39 | import scala.collection.convert.decorateAsScala._ 40 | import scala.concurrent.ExecutionContext.Implicits.global 41 | import ParHtmlSpecSearch.getHtmlSpec 42 | import ch4.FuturesCallbacks.getUrlSpec 43 | 44 | def intersection(a: GenSet[String], b: GenSet[String]): GenSet[String] = { 45 | val skiplist = new ConcurrentSkipListSet[String] 46 | for (x <- a.par) if (b contains x) skiplist.add(x) 47 | val result: Set[String] = skiplist.asScala 48 | result 49 | } 50 | 51 | val ifut = for { 52 | htmlSpec <- getHtmlSpec() 53 | urlSpec <- getUrlSpec() 54 | } yield { 55 | val htmlWords = htmlSpec.mkString.split("\\s+").toSet 56 | val urlWords = urlSpec.mkString.split("\\s+").toSet 57 | intersection(htmlWords, urlWords) 58 | } 59 | 60 | ifut foreach { case i => 61 | log(s"intersection = $i") 62 | } 63 | 64 | } 65 | 66 | 67 | object ConcurrentCollectionsBad extends App { 68 | import java.util.concurrent.ConcurrentSkipListSet 69 | import scala.collection._ 70 | import scala.collection.parallel._ 71 | 72 | def toPar[T](c: ConcurrentSkipListSet[T]): ParSet[T] = ??? 73 | 74 | val c = new ConcurrentSkipListSet[Int] 75 | for (i <- 0 until 100) c.add(i) 76 | 77 | for (x <- toPar(c)) c.add(x) // bad 78 | } 79 | 80 | 81 | object ConcurrentTrieMap extends App { 82 | import scala.collection._ 83 | 84 | val cache = new concurrent.TrieMap[Int, String]() 85 | for (i <- 0 until 100) cache(i) = i.toString 86 | 87 | for ((number, string) <- cache.par) cache(-number) = s"-$string" 88 | 89 | log(s"cache - ${cache.keys.toList.sorted}") 90 | } 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/ch5/Custom.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package ch5 3 | 4 | 5 | 6 | import scala.collection.parallel._ 7 | 8 | 9 | 10 | class ParString(val str: String) 11 | extends immutable.ParSeq[Char] { 12 | def apply(i: Int) = str.charAt(i) 13 | 14 | def length = str.length 15 | 16 | def seq = new collection.immutable.WrappedString(str) 17 | 18 | def splitter = new ParStringSplitter(str, 0, str.length) 19 | 20 | override def newCombiner = new ParStringCombiner 21 | 22 | } 23 | 24 | 25 | class ParStringSplitter(private val s: String, private var i: Int, private val limit: Int) extends SeqSplitter[Char] { 26 | final def hasNext = i < limit 27 | final def next = { 28 | val r = s.charAt(i) 29 | i += 1 30 | r 31 | } 32 | def remaining = limit - i 33 | def dup = new ParStringSplitter(s, i, limit) 34 | def split = { 35 | val rem = remaining 36 | if (rem >= 2) psplit(rem / 2, rem - rem / 2) 37 | else Seq(this) 38 | } 39 | def psplit(sizes: Int*): Seq[ParStringSplitter] = { 40 | val ss = for (sz <- sizes) yield { 41 | val nlimit = (i + sz) min limit 42 | val ps = new ParStringSplitter(s, i, nlimit) 43 | i = nlimit 44 | ps 45 | } 46 | if (i == limit) ss else ss :+ new ParStringSplitter(s, i, limit) 47 | } 48 | } 49 | 50 | 51 | class ParStringCombiner extends Combiner[Char, ParString] { 52 | import scala.collection.mutable.ArrayBuffer 53 | private var sz = 0 54 | private val chunks = new ArrayBuffer += new StringBuilder 55 | private var lastc = chunks.last 56 | 57 | def size: Int = sz 58 | 59 | def +=(elem: Char): this.type = { 60 | lastc += elem 61 | sz += 1 62 | this 63 | } 64 | 65 | def clear = { 66 | chunks.clear 67 | chunks += new StringBuilder 68 | lastc = chunks.last 69 | sz = 0 70 | } 71 | 72 | def result: ParString = { 73 | val rsb = new StringBuilder 74 | for (sb <- chunks) rsb.append(sb) 75 | new ParString(rsb.toString) 76 | } 77 | 78 | def combine[U <: Char, NewTo >: ParString](that: Combiner[U, NewTo]) = 79 | if (that eq this) this else that match { 80 | case that: ParStringCombiner => 81 | sz += that.sz 82 | chunks ++= that.chunks 83 | lastc = chunks.last 84 | this 85 | } 86 | } 87 | 88 | 89 | object CustomCharCount extends App { 90 | val txt = "A custom text " * 250000 91 | val partxt = new ParString(txt) 92 | 93 | val seqtime = warmedTimed(50) { 94 | txt.foldLeft(0)((n, c) => if (Character.isUpperCase(c)) n + 1 else n) 95 | } 96 | 97 | log(s"Sequential time - $seqtime ms") 98 | 99 | val partime = warmedTimed(50) { 100 | partxt.aggregate(0)((n, c) => if (Character.isUpperCase(c)) n + 1 else n, _ + _) 101 | } 102 | 103 | log(s"Parallel time - $partime ms") 104 | 105 | } 106 | 107 | 108 | object CustomCharFilter extends App { 109 | val txt = "A custom txt" * 25000 110 | val partxt = new ParString(txt) 111 | 112 | val seqtime = warmedTimed(250) { 113 | txt.filter(_ != ' ') 114 | } 115 | 116 | log(s"Sequential time - $seqtime ms") 117 | 118 | val partime = warmedTimed(250) { 119 | partxt.filter(_ != ' ') 120 | } 121 | 122 | log(s"Parallel time - $partime ms") 123 | 124 | } 125 | 126 | 127 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/ch5/Par.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package ch5 3 | 4 | 5 | 6 | 7 | 8 | 9 | object ParBasic extends App { 10 | import scala.collection._ 11 | 12 | val numbers = scala.util.Random.shuffle(Vector.tabulate(5000000)(i => i)) 13 | 14 | val seqtime = timed { 15 | val n = numbers.max 16 | println(s"largest number $n") 17 | } 18 | 19 | log(s"Sequential time $seqtime ms") 20 | 21 | val partime = timed { 22 | val n = numbers.par.max 23 | println(s"largest number $n") 24 | } 25 | 26 | log(s"Parallel time $partime ms") 27 | } 28 | 29 | 30 | object ParUid extends App { 31 | import scala.collection._ 32 | import java.util.concurrent.atomic._ 33 | private val uid = new AtomicLong(0L) 34 | 35 | val seqtime = timed { 36 | for (i <- 0 until 10000000) uid.incrementAndGet() 37 | } 38 | log(s"Sequential time $seqtime ms") 39 | 40 | val partime = timed { 41 | for (i <- (0 until 10000000).par) uid.incrementAndGet() 42 | } 43 | log(s"Parallel time $partime ms") 44 | 45 | } 46 | 47 | 48 | object ParGeneric extends App { 49 | import scala.collection._ 50 | import scala.io.Source 51 | 52 | def findLongestLine(xs: GenSeq[String]): Unit = { 53 | val line = xs.maxBy(_.length) 54 | log(s"Longest line - $line") 55 | } 56 | 57 | val doc = Array.tabulate(1000)(i => "lorem ipsum " * (i % 10)) 58 | 59 | findLongestLine(doc) 60 | findLongestLine(doc.par) 61 | 62 | } 63 | 64 | 65 | object ParConfig extends App { 66 | import scala.collection._ 67 | import scala.concurrent.forkjoin.ForkJoinPool 68 | 69 | val fjpool = new ForkJoinPool(2) 70 | val myTaskSupport = new parallel.ForkJoinTaskSupport(fjpool) 71 | val numbers = scala.util.Random.shuffle(Vector.tabulate(5000000)(i => i)) 72 | val partime = timed { 73 | val parnumbers = numbers.par 74 | parnumbers.tasksupport = myTaskSupport 75 | val n = parnumbers.max 76 | println(s"largest number $n") 77 | } 78 | log(s"Parallel time $partime ms") 79 | } 80 | 81 | 82 | object ParHtmlSpecSearch extends App { 83 | import scala.concurrent._ 84 | import ExecutionContext.Implicits.global 85 | import scala.collection._ 86 | import scala.io.Source 87 | 88 | def getHtmlSpec() = Future { 89 | val specSrc: Source = Source.fromURL("http://www.w3.org/MarkUp/html-spec/html-spec.txt") 90 | try specSrc.getLines.toArray finally specSrc.close() 91 | } 92 | 93 | getHtmlSpec() foreach { case specDoc => 94 | log(s"Download complete!") 95 | 96 | def search(d: GenSeq[String]) = warmedTimed() { 97 | d.indexWhere(line => line.matches(".*TEXTAREA.*")) 98 | } 99 | 100 | val seqtime = search(specDoc) 101 | log(s"Sequential time $seqtime ms") 102 | 103 | val partime = search(specDoc.par) 104 | log(s"Parallel time $partime ms") 105 | } 106 | 107 | } 108 | 109 | 110 | object ParNonParallelizableCollections extends App { 111 | import scala.collection._ 112 | 113 | val list = List.fill(1000000)("") 114 | val vector = Vector.fill(1000000)("") 115 | log(s"list conversion time: ${timed(list.par)} ms") 116 | log(s"vector conversion time: ${timed(vector.par)} ms") 117 | } 118 | 119 | 120 | object ParNonParallelizableOperations extends App { 121 | import scala.collection._ 122 | import scala.concurrent.ExecutionContext.Implicits.global 123 | import ParHtmlSpecSearch.getHtmlSpec 124 | 125 | getHtmlSpec() foreach { case specDoc => 126 | def allMatches(d: GenSeq[String]) = warmedTimed() { 127 | val results = d.foldLeft("")((acc, line) => if (line.matches(".*TEXTAREA.*")) s"$acc\n$line" else acc) 128 | // Note: must use "aggregate" instead of "foldLeft"! 129 | } 130 | 131 | val seqtime = allMatches(specDoc) 132 | log(s"Sequential time - $seqtime ms") 133 | 134 | val partime = allMatches(specDoc.par) 135 | log(s"Parallel time - $partime ms") 136 | } 137 | } 138 | 139 | 140 | object ParNonDeterministicOperation extends App { 141 | import scala.collection._ 142 | import scala.concurrent.ExecutionContext.Implicits.global 143 | import ParHtmlSpecSearch.getHtmlSpec 144 | 145 | getHtmlSpec() foreach { case specDoc => 146 | val seqresult = specDoc.find(line => line.matches(".*TEXTAREA.*")) 147 | val parresult = specDoc.par.find(line => line.matches(".*TEXTAREA.*")) 148 | log(s"Sequential result - $seqresult") 149 | log(s"Parallel result - $parresult") 150 | } 151 | } 152 | 153 | 154 | object ParNonCommutativeOperator extends App { 155 | import scala.collection._ 156 | 157 | val doc = mutable.ArrayBuffer.tabulate(20)(i => s"Page $i, ") 158 | def test(doc: GenIterable[String]) { 159 | val seqtext = doc.seq.reduceLeft(_ + _) 160 | val partext = doc.par.reduce(_ + _) 161 | log(s"Sequential result - $seqtext\n") 162 | log(s"Parallel result - $partext\n") 163 | } 164 | test(doc) 165 | test(doc.toSet) 166 | } 167 | 168 | 169 | object ParNonAssociativeOperator extends App { 170 | import scala.collection._ 171 | 172 | def test(doc: GenIterable[Int]) { 173 | val seqtext = doc.seq.reduceLeft(_ - _) 174 | val partext = doc.par.reduce(_ - _) 175 | log(s"Sequential result - $seqtext\n") 176 | log(s"Parallel result - $partext\n") 177 | } 178 | test(0 until 30) 179 | } 180 | 181 | 182 | object ParMultipleOperators extends App { 183 | import scala.collection._ 184 | import scala.concurrent.ExecutionContext.Implicits.global 185 | import ParHtmlSpecSearch.getHtmlSpec 186 | 187 | getHtmlSpec() foreach { case specDoc => 188 | val length = specDoc.aggregate(0)( 189 | (count: Int, line: String) => count + line.length, 190 | (count1: Int, count2: Int) => count1 + count2 191 | ) 192 | log(s"Total characters in HTML spec - $length") 193 | } 194 | } 195 | 196 | 197 | object ParSideEffectsIncorrect extends App { 198 | import scala.collection._ 199 | 200 | def intSize(a: GenSet[Int], b: GenSet[Int]): Int = { 201 | var count = 0 202 | for (x <- a) if (b contains x) count += 1 203 | count 204 | } 205 | val seqres = intSize((0 until 1000).toSet, (0 until 1000 by 4).toSet) 206 | val parres = intSize((0 until 1000).par.toSet, (0 until 1000 by 4).par.toSet) 207 | log(s"Sequential result - $seqres") 208 | log(s"Parallel result - $parres") 209 | } 210 | 211 | 212 | object ParSideEffectsCorrect extends App { 213 | import scala.collection._ 214 | import java.util.concurrent.atomic._ 215 | 216 | def intSize(a: GenSet[Int], b: GenSet[Int]): Int = { 217 | val count = new AtomicInteger(0) 218 | for (x <- a) if (b contains x) count.incrementAndGet() 219 | count.get 220 | } 221 | val seqres = intSize((0 until 1000).toSet, (0 until 1000 by 4).toSet) 222 | val parres = intSize((0 until 1000).par.toSet, (0 until 1000 by 4).par.toSet) 223 | log(s"Sequential result - $seqres") 224 | log(s"Parallel result - $parres") 225 | } 226 | 227 | 228 | object ParMutableWrong extends App { 229 | import scala.collection._ 230 | 231 | val buffer = mutable.ArrayBuffer[Int]() ++= (0 until 250) 232 | for (x <- buffer.par) buffer += x 233 | log(buffer.toString) 234 | } 235 | 236 | 237 | 238 | 239 | 240 | 241 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/ch5/package.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | 3 | 4 | 5 | 6 | 7 | 8 | package object ch5 { 9 | @volatile var dummy: Any = _ 10 | 11 | def timed[T](body: =>T): Double = { 12 | val start = System.nanoTime 13 | dummy = body 14 | val end = System.nanoTime 15 | ((end - start) / 1000) / 1000.0 16 | } 17 | 18 | def warmedTimed[T](times: Int = 200)(body: =>T): Double = { 19 | for (_ <- 0 until times) body 20 | timed(body) 21 | } 22 | } 23 | 24 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/ch6/Alternative.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package ch6 3 | 4 | 5 | 6 | import scala.reactive._ 7 | 8 | 9 | 10 | object AkkaStreams extends App { 11 | import akka.actor.ActorSystem 12 | import akka.stream._ 13 | import akka.stream.scaladsl._ 14 | 15 | implicit val system = ActorSystem("system") 16 | 17 | val numbers = Iterator.from(1).take(100) 18 | 19 | def isPrime(n: Int) = (2 to (n - 1)).forall(n % _ != 0) 20 | 21 | Flow(numbers) 22 | .filter(isPrime) 23 | .map(num => s"Prime number: $num") 24 | .foreach(println) 25 | .onComplete(FlowMaterializer(MaterializerSettings())) { 26 | case _ => system.shutdown() 27 | } 28 | 29 | } 30 | 31 | 32 | object ReactiveCollections extends App { 33 | 34 | def isPrime(n: Int) = (2 to (n - 1)).forall(n % _ != 0) 35 | 36 | val numbers = new Reactive.Emitter[Int] 37 | numbers 38 | .filter(isPrime) 39 | .map(num => s"Prime number: $num") 40 | .foreach(println) 41 | 42 | for (i <- 0 until 100) numbers += i 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/ch6/Composition.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package ch6 3 | 4 | 5 | 6 | 7 | 8 | 9 | object CompositionMapAndFilter extends App { 10 | import rx.lang.scala._ 11 | import scala.concurrent.duration._ 12 | 13 | val odds = Observable.interval(0.5.seconds).filter(_ % 2 == 1).map(n => s"odd number $n").take(5) 14 | odds.subscribe(log _, e => log(s"unexpected $e"), () => log("no more odds")) 15 | 16 | val evens = for (n <- Observable.from(0 until 9); if n % 2 == 0) yield s"even number $n" 17 | evens.subscribe(log _) 18 | 19 | } 20 | 21 | 22 | object CompositionConcatAndFlatten extends App { 23 | import rx.lang.scala._ 24 | import scala.concurrent._ 25 | import scala.concurrent.duration._ 26 | import ExecutionContext.Implicits.global 27 | import scala.io.Source 28 | 29 | def fetchQuote(): Future[String] = Future { 30 | blocking { 31 | val url = "http://quotes.stormconsultancy.co.uk/random.json" 32 | Source.fromURL(url).getLines.mkString 33 | } 34 | } 35 | 36 | def fetchQuoteObservable(): Observable[String] = Observable.from(fetchQuote()) 37 | 38 | def quotes: Observable[Observable[String]] = Observable.interval(0.5.seconds).take(5).map { 39 | n => fetchQuoteObservable().map(txt => s"$n) $txt") 40 | } 41 | 42 | log(s"Using concat") 43 | quotes.concat.subscribe(log _) 44 | 45 | Thread.sleep(6000) 46 | 47 | log(s"Now using flatten") 48 | quotes.flatten.subscribe(log _) 49 | 50 | Thread.sleep(6000) 51 | 52 | log(s"Now using flatMap") 53 | Observable.interval(0.5.seconds).take(5).flatMap({ 54 | n => fetchQuoteObservable().map(txt => s"$n) $txt") 55 | }).subscribe(log _) 56 | 57 | Thread.sleep(6000) 58 | 59 | log(s"Now using good ol' for-comprehensions") 60 | val qs = for { 61 | n <- Observable.interval(0.5.seconds).take(5) 62 | txt <- fetchQuoteObservable() 63 | } yield s"$n) $txt" 64 | qs.subscribe(log _) 65 | 66 | Thread.sleep(6000) 67 | 68 | } 69 | 70 | 71 | // There is always one more bug. 72 | object CompositionRetry extends App { 73 | import rx.lang.scala._ 74 | import scala.concurrent.duration._ 75 | import scala.io.Source 76 | import Observable._ 77 | 78 | def randomQuote = Observable.apply[String] { obs => 79 | val url = "http://www.iheartquotes.com/api/v1/random?" + 80 | "show_permalink=false&show_source=false" 81 | obs.onNext(Source.fromURL(url).getLines.mkString) 82 | obs.onCompleted() 83 | Subscription() 84 | } 85 | 86 | def errorMessage = items("Retrying...") ++ error(new Exception) 87 | 88 | def shortQuote = for { 89 | txt <- randomQuote 90 | message <- if (txt.length < 100) items(txt) else errorMessage 91 | } yield message 92 | 93 | shortQuote.retry(5).subscribe(log _, e => log(s"too long - $e"), () => log("done!")) 94 | 95 | } 96 | 97 | 98 | object CompositionScan extends App { 99 | import rx.lang.scala._ 100 | 101 | CompositionRetry.shortQuote.retry.repeat.take(100).scan(0) { 102 | (n, q) => if (q == "Retrying...") n + 1 else n 103 | } subscribe(n => log(s"$n / 100")) 104 | } 105 | 106 | 107 | object CompositionReduce extends App { 108 | import rx.lang.scala._ 109 | import scala.concurrent.duration._ 110 | 111 | def shortQuote = CompositionRetry.randomQuote.retry.take(5).filter(_ != "Retrying...") 112 | val shortQuotesCollection = (shortQuote ++ shortQuote ++ shortQuote).foldLeft("") { (acc, q) => 113 | s"$acc$q\n\n" 114 | } 115 | 116 | shortQuotesCollection.subscribe(log _) 117 | 118 | } 119 | 120 | 121 | object CompositionErrors extends App { 122 | import rx.lang.scala._ 123 | 124 | val status = Observable[String] { sub => 125 | sub.onNext("ok") 126 | sub.onNext("still ok") 127 | sub.onError(new Exception("very bad")) 128 | } 129 | 130 | val fixedStatus = status.onErrorReturn(e => e.getMessage) 131 | fixedStatus.subscribe(log _) 132 | 133 | val continuedStatus = status.onErrorResumeNext(e => Observable.items("better", "much better")) 134 | continuedStatus.subscribe(log _) 135 | 136 | } 137 | 138 | 139 | 140 | 141 | 142 | 143 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/ch6/Observables.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package ch6 3 | 4 | 5 | 6 | 7 | 8 | 9 | object ObservablesItems extends App { 10 | import rx.lang.scala._ 11 | 12 | val o = Observable.items("Pascal", "Java", "Scala") 13 | o.subscribe(name => log(s"learned the $name language")) 14 | o.subscribe(name => log(s"forgot the $name language")) 15 | 16 | } 17 | 18 | 19 | object ObservablesTimer extends App { 20 | import rx.lang.scala._ 21 | import scala.concurrent.duration._ 22 | 23 | val o = Observable.timer(1.second) 24 | o.subscribe(_ => log(s"Timeout!")) 25 | o.subscribe(_ => log(s"Another timeout!")) 26 | } 27 | 28 | 29 | object ObservablesExceptions extends App { 30 | import rx.lang.scala._ 31 | 32 | val o = Observable.items(1, 2) ++ Observable.error(new RuntimeException) ++ Observable.items(3, 4) 33 | o.subscribe( 34 | x => log(s"number $x"), 35 | t => log(s"an error occurred: $t") 36 | ) 37 | } 38 | 39 | 40 | object ObservablesLifetime extends App { 41 | import rx.lang.scala._ 42 | 43 | val classics = List("Il buono, il brutto, il cattivo.", "Back to the future", "Die Hard") 44 | val o = Observable.from(classics) 45 | 46 | o.subscribe(new Observer[String] { 47 | override def onNext(m: String) = log(s"Movies Watchlist - $m") 48 | override def onError(e: Throwable) = log(s"Ooops - $e!") 49 | override def onCompleted() = log(s"No more movies.") 50 | }) 51 | } 52 | 53 | 54 | object ObservablesCreate extends App { 55 | import rx.lang.scala._ 56 | 57 | val vms = Observable.apply[String] { obs => 58 | obs.onNext("JVM") 59 | obs.onNext(".NET") 60 | obs.onNext("DartVM") 61 | obs.onCompleted() 62 | Subscription() 63 | } 64 | 65 | log(s"About to subscribe") 66 | vms.subscribe(log _, e => log(s"oops - $e"), () => log("Done!")) 67 | log(s"Subscription returned") 68 | 69 | } 70 | 71 | 72 | object ObservablesCreateFuture extends App { 73 | import rx.lang.scala._ 74 | import scala.concurrent._ 75 | import ExecutionContext.Implicits.global 76 | 77 | val f = Future { 78 | Thread.sleep(500) 79 | "Back to the Future(s)" 80 | } 81 | 82 | val o = Observable.apply[String] { obs => 83 | f foreach { 84 | case s => 85 | obs.onNext(s) 86 | obs.onCompleted() 87 | } 88 | f.failed foreach { 89 | case t => obs.onError(t) 90 | } 91 | Subscription() 92 | } 93 | 94 | o.subscribe(log _) 95 | 96 | } 97 | 98 | 99 | object ObservablesFromFuture extends App { 100 | import rx.lang.scala._ 101 | import scala.concurrent._ 102 | import ExecutionContext.Implicits.global 103 | 104 | val o = Observable.from(Future { 105 | Thread.sleep(500) 106 | "Back to the Future(s)" 107 | }) 108 | 109 | o.subscribe(log _) 110 | } 111 | 112 | 113 | object ObservablesCombinators extends App { 114 | import rx.lang.scala._ 115 | 116 | val roles = Observable.items("The Good", "The Bad", "The Ugly") 117 | val names = Observable.items("Clint Eastwood", "Lee Van Cleef", "Eli Wallach") 118 | val zipped = names.zip(roles).map { case (name, role) => s"$name - $role" } 119 | 120 | zipped.subscribe(log _) 121 | 122 | } 123 | 124 | 125 | object ObservablesSubscriptions extends App { 126 | import rx.lang.scala._ 127 | import org.apache.commons.io.monitor._ 128 | 129 | def modifiedFiles(directory: String): Observable[String] = { 130 | Observable.apply { observer => 131 | val fileMonitor = new FileAlterationMonitor(1000) 132 | val fileObs = new FileAlterationObserver(directory) 133 | val fileLis = new FileAlterationListenerAdaptor { 134 | override def onFileChange(file: java.io.File) { 135 | observer.onNext(file.getName) 136 | } 137 | } 138 | fileObs.addListener(fileLis) 139 | fileMonitor.addObserver(fileObs) 140 | fileMonitor.start() 141 | 142 | Subscription { fileMonitor.stop() } 143 | } 144 | } 145 | 146 | log(s"starting to monitor files") 147 | val subscription = modifiedFiles(".").subscribe(filename => log(s"$filename modified!")) 148 | log(s"please modify and save a file") 149 | 150 | Thread.sleep(10000) 151 | 152 | subscription.unsubscribe() 153 | log(s"monitoring done") 154 | 155 | } 156 | 157 | 158 | object ObservablesHot extends App { 159 | import rx.lang.scala._ 160 | import org.apache.commons.io.monitor._ 161 | 162 | val fileMonitor = new FileAlterationMonitor(1000) 163 | fileMonitor.start() 164 | 165 | def modifiedFiles(directory: String): Observable[String] = { 166 | val fileObs = new FileAlterationObserver(directory) 167 | fileMonitor.addObserver(fileObs) 168 | Observable.apply { observer => 169 | val fileLis = new FileAlterationListenerAdaptor { 170 | override def onFileChange(file: java.io.File) { 171 | observer.onNext(file.getName) 172 | } 173 | } 174 | fileObs.addListener(fileLis) 175 | 176 | Subscription { fileObs.removeListener(fileLis) } 177 | } 178 | } 179 | 180 | log(s"first subscribe call") 181 | val subscription1 = modifiedFiles(".").subscribe(filename => log(s"$filename modified!")) 182 | 183 | Thread.sleep(6000) 184 | 185 | log(s"another subscribe call") 186 | val subscription2 = modifiedFiles(".").subscribe(filename => log(s"$filename changed!")) 187 | 188 | Thread.sleep(6000) 189 | 190 | log(s"unsubscribed second call") 191 | subscription2.unsubscribe() 192 | 193 | Thread.sleep(6000) 194 | 195 | fileMonitor.stop() 196 | 197 | } 198 | 199 | 200 | object ObservablesHotVsCold extends App { 201 | import java.util.{Timer, TimerTask} 202 | import scala.collection._ 203 | import rx.lang.scala._ 204 | 205 | val songs = List("Eye of the Tiger", "You Spin Me Round", "Rebel Yell") 206 | val myPlaylist = Observable.from(songs) 207 | 208 | object Player extends TimerTask { 209 | val timer = new Timer 210 | var index = 0 211 | var subscribers = mutable.Set[Subscriber[String]]() 212 | def start() = timer.schedule(this, 0L, 1000L) 213 | def stop() = timer.cancel() 214 | 215 | def run() { 216 | index = (index + 1) % songs.length 217 | Player.synchronized { for (s <- subscribers) s.onNext(songs(index)) } 218 | } 219 | def turnOn(s: Subscriber[String]) = Player.synchronized { subscribers += s } 220 | def turnOff(s: Subscriber[String]) = Player.synchronized { subscribers -= s } 221 | } 222 | Player.start() 223 | 224 | val currentlyPlaying = Observable[String] { subscriber => 225 | Player.turnOn(subscriber) 226 | subscriber.add(Subscription { Player.turnOff(subscriber) }) 227 | } 228 | 229 | log(s"adding to a cold observable") 230 | myPlaylist.subscribe(log _) 231 | log(s"adding to a cold observable again") 232 | myPlaylist.subscribe(log _) 233 | Thread.sleep(2000) 234 | 235 | log(s"adding to a hot observable") 236 | val subscription1 = currentlyPlaying.subscribe(log _) 237 | Thread.sleep(2400) 238 | subscription1.unsubscribe() 239 | Thread.sleep(1200) 240 | log(s"adding to a hot observable again") 241 | val subscription2 = currentlyPlaying.subscribe(log _) 242 | Thread.sleep(2400) 243 | subscription2.unsubscribe() 244 | Thread.sleep(1200) 245 | log(s"Done -- shutting down the Player") 246 | Player.stop() 247 | 248 | } 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/ch6/Schedulers.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package ch6 3 | 4 | 5 | 6 | 7 | 8 | 9 | object SchedulersComputation extends App { 10 | import rx.lang.scala._ 11 | 12 | val scheduler = schedulers.ComputationScheduler() 13 | val numbers = Observable.from(0 until 20) 14 | numbers.subscribe(n => log(s"num $n")) 15 | numbers.observeOn(scheduler).subscribe(n => log(s"num $n")) 16 | 17 | } 18 | 19 | 20 | object SchedulersSwing extends scala.swing.SimpleSwingApplication { 21 | import rx.lang.scala._ 22 | import scala.swing._ 23 | import scala.swing.event._ 24 | 25 | def top = new MainFrame { 26 | title = "Swing Observables" 27 | 28 | val button = new Button { 29 | text = "Click" 30 | } 31 | 32 | contents = button 33 | 34 | val buttonClicks = Observable[Unit] { sub => 35 | button.reactions += { 36 | case ButtonClicked(_) => sub.onNext(()) 37 | } 38 | } 39 | 40 | buttonClicks.subscribe(_ => log("button clicked")) 41 | } 42 | 43 | } 44 | 45 | 46 | object SchedulersBrowser extends scala.swing.SimpleSwingApplication { 47 | import rx.lang.scala._ 48 | import scala.concurrent._ 49 | import ExecutionContext.Implicits.global 50 | import scala.concurrent.duration._ 51 | import scala.swing._ 52 | import scala.swing.event._ 53 | import scala.io.Source 54 | import java.util.concurrent.Executor 55 | 56 | abstract class BrowserFrame extends MainFrame { 57 | title = "MiniBrowser" 58 | 59 | val termfield = new TextField("http://www.w3.org/Addressing/URL/url-spec.txt") 60 | val pagefield = new TextArea 61 | val button = new Button { 62 | text = "Feeling Lucky" 63 | } 64 | 65 | contents = new BorderPanel { 66 | import BorderPanel.Position._ 67 | layout(new BorderPanel { 68 | layout(new Label("URL:")) = West 69 | layout(termfield) = Center 70 | layout(button) = East 71 | }) = North 72 | layout(pagefield) = Center 73 | } 74 | 75 | size = new Dimension(1024, 768) 76 | } 77 | 78 | trait BrowserLogic { 79 | self: BrowserFrame => 80 | 81 | def suggestRequest(term: String): Observable[String] = { 82 | val url = s"http://suggestqueries.google.com/complete/search?client=firefox&q=$term" 83 | val request = Future { Source.fromURL(url).mkString } 84 | Observable.from(request).timeout(0.5.seconds).onErrorResumeNext(Observable.items("(no suggestions)")) 85 | } 86 | 87 | def pageRequest(url: String): Observable[String] = { 88 | val request = Future { Source.fromURL(url).mkString } 89 | Observable.from(request).timeout(4.seconds).onErrorResumeNext(t => Observable.items(s"Could not load page: $t")) 90 | } 91 | 92 | termfield.texts.map(suggestRequest).concat.observeOn(swingScheduler).subscribe { 93 | response => pagefield.text = response 94 | } 95 | 96 | button.clicks.map(_ => pageRequest(termfield.text)).concat.observeOn(swingScheduler).subscribe { 97 | response => pagefield.text = response 98 | } 99 | } 100 | 101 | def top = new BrowserFrame with BrowserLogic 102 | 103 | } 104 | 105 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/ch6/Subjects.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package ch6 3 | 4 | 5 | 6 | 7 | 8 | 9 | object SubjectsOS extends App { 10 | import rx.lang.scala._ 11 | import scala.concurrent.duration._ 12 | import ObservablesSubscriptions._ 13 | 14 | object RxOS { 15 | val messageBus = Subject[String]() 16 | messageBus.subscribe(log _) 17 | } 18 | 19 | object TimeModule { 20 | val systemClock = Observable.interval(1.seconds).map(t => s"systime: $t") 21 | } 22 | 23 | object FileSystemModule { 24 | val fileModifications = modifiedFiles(".").map(filename => s"file modification: $filename") 25 | } 26 | 27 | log(s"RxOS booting...") 28 | val modules = List( 29 | TimeModule.systemClock, 30 | FileSystemModule.fileModifications 31 | ) 32 | val loadedModules = modules.map(_.subscribe(RxOS.messageBus)) 33 | log(s"RxOS boot sequence finished!") 34 | 35 | Thread.sleep(10000) 36 | for (mod <- loadedModules) mod.unsubscribe() 37 | log(s"RxOS going for shutdown") 38 | 39 | } 40 | 41 | 42 | object SubjectsOSLog extends App { 43 | import rx.lang.scala._ 44 | import SubjectsOS.{TimeModule, FileSystemModule} 45 | 46 | object RxOS { 47 | val messageBus = Subject[String]() 48 | val messageLog = subjects.ReplaySubject[String]() 49 | messageBus.subscribe(log _) 50 | messageBus.subscribe(messageLog) 51 | } 52 | 53 | val loadedModules = List( 54 | TimeModule.systemClock, 55 | FileSystemModule.fileModifications 56 | ).map(_.subscribe(RxOS.messageBus)) 57 | 58 | log(s"RxOS booting") 59 | Thread.sleep(1000) 60 | log(s"RxOS booted!") 61 | Thread.sleep(10000) 62 | for (mod <- loadedModules) mod.unsubscribe() 63 | log(s"RxOS dumping the complete event log") 64 | RxOS.messageLog.subscribe(log _) 65 | log(s"RxOS going for shutdown") 66 | 67 | } 68 | 69 | 70 | object SubjectsOSRegistry extends App { 71 | import rx.lang.scala._ 72 | 73 | object KernelModuleC { 74 | private val newKeys = Subject[(String, String)]() 75 | val registry = subjects.BehaviorSubject(Map[String, String]()) 76 | newKeys.scan(Map[String, String]())(_ + _).subscribe(registry) 77 | def add(kv: (String, String)) = newKeys.onNext(kv) 78 | } 79 | 80 | KernelModuleC.registry.subscribe(reg => log(s"App A sees registry $reg")) 81 | 82 | log("RxOS about to add home dir") 83 | Thread.sleep(1000) 84 | KernelModuleC.add("dir.home" -> "/home/") 85 | 86 | object KernelModuleD { 87 | type Reg = Map[String, String] 88 | val registryDiffs = KernelModuleC.registry.scan((prev: Reg, curr: Reg) => curr -- prev.keys).drop(1) 89 | } 90 | KernelModuleD.registryDiffs.subscribe(diff => log(s"App B detects registry change: $diff")) 91 | 92 | log("RxOS about to add root dir") 93 | Thread.sleep(1000) 94 | KernelModuleC.add("dir.root" -> "/root/") 95 | 96 | } 97 | 98 | 99 | object SubjectsAsync extends App { 100 | import rx.lang.scala._ 101 | 102 | object ProcessModule { 103 | private val added = Subject[Either[Int, Int]]() 104 | private val ended = Subject[Either[Int, Int]]() 105 | private val events = (added merge ended).scan(Set[Int]()) { 106 | case (set, Right(pid)) => set + pid 107 | case (set, Left(pid)) => set - pid 108 | } 109 | val processes = subjects.AsyncSubject[Set[Int]]() 110 | events.subscribe(processes) 111 | def add(pid: Int) = added.onNext(Right(pid)) 112 | def end(pid: Int) = ended.onNext(Left(pid)) 113 | } 114 | 115 | ProcessModule.add(1) 116 | ProcessModule.add(2) 117 | ProcessModule.add(3) 118 | 119 | log("RxOS processes started") 120 | Thread.sleep(1000) 121 | log("RxOS going for shutdown!") 122 | 123 | ProcessModule.end(1) 124 | ProcessModule.processes.subscribe(pids => log(s"need to force-kill processes ${pids.mkString(",")}")) 125 | Thread.sleep(1000) 126 | ProcessModule.processes.onCompleted() 127 | 128 | } 129 | 130 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/ch6/package.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | 3 | 4 | 5 | import scala.swing._ 6 | import scala.swing.event._ 7 | import javax.swing._ 8 | import java.awt.event._ 9 | import rx.lang.scala._ 10 | import java.util.concurrent.Executor 11 | 12 | 13 | 14 | package object ch6 { 15 | 16 | implicit class ButtonOps(val self: Button) { 17 | def clicks = Observable[Unit] { sub => 18 | self.reactions += { 19 | case ButtonClicked(_) => sub.onNext(()) 20 | } 21 | } 22 | } 23 | 24 | implicit class TextFieldOps(val self: TextField) { 25 | def texts = Observable[String] { sub => 26 | self.reactions += { 27 | case ValueChanged(_) => sub.onNext(self.text) 28 | } 29 | } 30 | } 31 | 32 | implicit class TableOps(val self: Table) { 33 | def rowDoubleClicks = Observable[Int] { sub => 34 | self.peer.addMouseListener(new MouseAdapter { 35 | override def mouseClicked(e: java.awt.event.MouseEvent) { 36 | if (e.getClickCount == 2) { 37 | val row = self.peer.getSelectedRow 38 | sub.onNext(row) 39 | } 40 | } 41 | }) 42 | } 43 | } 44 | 45 | def swing(body: =>Unit) = { 46 | val r = new Runnable { 47 | def run() = body 48 | } 49 | javax.swing.SwingUtilities.invokeLater(r) 50 | } 51 | 52 | val swingScheduler = new Scheduler { 53 | val asJavaScheduler = rx.schedulers.Schedulers.from(new Executor { 54 | def execute(r: Runnable) = javax.swing.SwingUtilities.invokeLater(r) 55 | }) 56 | } 57 | 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/ch7/Atomic.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package ch7 3 | 4 | 5 | 6 | 7 | 8 | 9 | object AtomicHistoryBad extends App { 10 | import java.util.concurrent.atomic._ 11 | import scala.annotation.tailrec 12 | import scala.concurrent._ 13 | import ExecutionContext.Implicits.global 14 | 15 | val urls = new AtomicReference[List[String]](Nil) 16 | val clen = new AtomicInteger(0) 17 | 18 | def addUrl(url: String): Unit = { 19 | @tailrec def append(): Unit = { 20 | val oldUrls = urls.get 21 | if (!urls.compareAndSet(oldUrls, url :: oldUrls)) append() 22 | } 23 | append() 24 | clen.addAndGet(url.length + 1) 25 | } 26 | 27 | def getUrlArray(): Array[Char] = { 28 | val array = new Array[Char](clen.get) 29 | val urlList = urls.get 30 | for ((character, i) <- urlList.map(_ + "\n").flatten.zipWithIndex) { 31 | array(i) = character 32 | } 33 | array 34 | } 35 | 36 | Future { 37 | try { log(s"sending: ${getUrlArray().mkString}") } 38 | catch { case e: Exception => log(s"problems getting the array $e") } 39 | } 40 | 41 | Future { 42 | addUrl("http://scala-lang.org") 43 | addUrl("https://github.com/scala/scala") 44 | addUrl("http://www.scala-lang.org/api") 45 | log("done browsing") 46 | } 47 | 48 | } 49 | 50 | 51 | object AtomicHistorySTM extends App { 52 | import scala.concurrent._ 53 | import ExecutionContext.Implicits.global 54 | import scala.concurrent.stm._ 55 | 56 | val urls = Ref[List[String]](Nil) 57 | val clen = Ref(0) 58 | 59 | def addUrl(url: String): Unit = atomic { implicit txn => 60 | urls() = url :: urls() 61 | clen() = clen() + url.length + 1 62 | } 63 | 64 | def getUrlArray(): Array[Char] = atomic { implicit txn => 65 | val array = new Array[Char](clen()) 66 | for ((character, i) <- urls().map(_ + "\n").flatten.zipWithIndex) { 67 | array(i) = character 68 | } 69 | array 70 | } 71 | 72 | Future { 73 | addUrl("http://scala-lang.org") 74 | addUrl("https://github.com/scala/scala") 75 | addUrl("http://www.scala-lang.org/api") 76 | log("done browsing") 77 | } 78 | 79 | Thread.sleep(25) 80 | 81 | Future { 82 | try { log(s"sending: ${getUrlArray().mkString}") } 83 | catch { case e: Exception => log(s"problems getting the array $e") } 84 | } 85 | 86 | } 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/ch7/Composition.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package ch7 3 | 4 | 5 | 6 | 7 | 8 | 9 | object CompositionSideEffects extends App { 10 | import scala.concurrent._ 11 | import ExecutionContext.Implicits.global 12 | import scala.concurrent.stm._ 13 | 14 | val myValue = Ref(0) 15 | 16 | def inc() = atomic { implicit txn => 17 | log(s"Incrementing ${myValue()}") 18 | myValue() = myValue() + 1 19 | } 20 | 21 | Future { inc() } 22 | Future { inc() } 23 | 24 | } 25 | 26 | 27 | object CompositionEscape extends App { 28 | import scala.concurrent._ 29 | import ExecutionContext.Implicits.global 30 | import scala.concurrent.stm._ 31 | 32 | val myValue = Ref(0) 33 | 34 | atomic { implicit txn => 35 | Future { 36 | Thread.sleep(500) 37 | myValue() = myValue() + 1 38 | } onComplete { 39 | case t => println(t) 40 | } 41 | } 42 | 43 | Thread.sleep(1000) 44 | } 45 | 46 | 47 | object CompositionCorrectSideEffect extends App { 48 | import scala.concurrent._ 49 | import ExecutionContext.Implicits.global 50 | import scala.concurrent.stm._ 51 | 52 | val myValue = Ref(0) 53 | 54 | def inc() = atomic { implicit txn => 55 | val valueAtStart = myValue() 56 | Txn.afterCommit { _ => 57 | log(s"Incrementing $valueAtStart") 58 | } 59 | myValue() = myValue() + 1 60 | } 61 | 62 | Future { inc() } 63 | Future { inc() } 64 | 65 | } 66 | 67 | 68 | object CompositionLoggingRollback extends App { 69 | import scala.concurrent._ 70 | import ExecutionContext.Implicits.global 71 | import scala.concurrent.stm._ 72 | 73 | val myValue = Ref(0) 74 | 75 | def inc() = atomic { implicit txn => 76 | Txn.afterRollback { _ => 77 | log(s"rollin' back") 78 | } 79 | myValue() = myValue() + 1 80 | } 81 | 82 | Future { inc() } 83 | Future { inc() } 84 | 85 | } 86 | 87 | 88 | object CompositionList extends App { 89 | import scala.concurrent._ 90 | import ExecutionContext.Implicits.global 91 | import scala.concurrent.stm._ 92 | 93 | case class Node(val elem: Int, val next: Ref[Node]) { 94 | def append(n: Node): Unit = atomic { implicit txn => 95 | val oldNext = next() 96 | next() = n 97 | n.next() = oldNext 98 | } 99 | def nextNode: Node = next.single() 100 | def appendIfEnd(n: Node) = next.single.transform { 101 | oldNext => if (oldNext == null) n else oldNext 102 | } 103 | } 104 | 105 | val nodes = Node(1, Ref(Node(4, Ref(Node(5, Ref(null)))))) 106 | val f = Future { nodes.append(Node(2, Ref(null))) } 107 | val g = Future { nodes.append(Node(3, Ref(null))) } 108 | 109 | for (_ <- f; _ <- g) log(s"Next node is: ${nodes.nextNode}") 110 | 111 | } 112 | 113 | 114 | object CompositionMutations extends App { 115 | import scala.concurrent._ 116 | import ExecutionContext.Implicits.global 117 | import scala.concurrent.stm._ 118 | import CompositionList.Node 119 | 120 | def nodeToString(n: Node): String = atomic { implicit txn => 121 | val b = new StringBuilder 122 | var curr = n 123 | while (curr != null) { 124 | b ++= s"${curr.elem}, " 125 | curr = curr.next() 126 | } 127 | b.toString 128 | } 129 | 130 | } 131 | 132 | 133 | object CompositionSortedList extends App { 134 | import scala.concurrent._ 135 | import ExecutionContext.Implicits.global 136 | import scala.concurrent.stm._ 137 | import CompositionList._ 138 | import CompositionMutations._ 139 | import scala.annotation.tailrec 140 | 141 | class TSortedList { 142 | val head = Ref[Node](null) 143 | 144 | override def toString = atomic { implicit txn => 145 | val headNode = head() 146 | nodeToString(headNode) 147 | } 148 | 149 | def insert(x: Int): this.type = atomic { implicit txn => 150 | @tailrec def insert(n: Node): Unit = { 151 | if (n.next() == null || n.next().elem > x) n.append(new Node(x, Ref(null))) 152 | else insert(n.next()) 153 | } 154 | 155 | if (head() == null || head().elem > x) head() = new Node(x, Ref(head())) 156 | else insert(head()) 157 | this 158 | } 159 | } 160 | 161 | val sortedList = new TSortedList 162 | 163 | val f = Future { sortedList.insert(1); sortedList.insert(4) } 164 | val g = Future { sortedList.insert(2); sortedList.insert(3) } 165 | 166 | for (_ <- f; _ <- g) log(s"sorted list - $sortedList") 167 | 168 | } 169 | 170 | 171 | object CompositionExceptions extends App { 172 | import scala.concurrent._ 173 | import ExecutionContext.Implicits.global 174 | import scala.concurrent.stm._ 175 | import CompositionSortedList._ 176 | 177 | def pop(lst: TSortedList, n: Int): Unit = atomic { implicit txn => 178 | var left = n 179 | while (left > 0) { 180 | lst.head() = lst.head().next() 181 | left -= 1 182 | } 183 | } 184 | 185 | val lst = new TSortedList 186 | lst.insert(4) 187 | lst.insert(9) 188 | lst.insert(1) 189 | lst.insert(16) 190 | 191 | Future { pop(lst, 2) } foreach { case _ => log(s"removed 2 elements - $lst") } 192 | Thread.sleep(1000) 193 | Future { pop(lst, 3) }.failed foreach { case t => log(s"oops $t - $lst") } 194 | Thread.sleep(1000) 195 | Future { 196 | atomic { implicit txn => 197 | pop(lst, 1) 198 | sys.error("") 199 | } 200 | }.failed foreach { case t => log(s"oops again $t - $lst") } 201 | Thread.sleep(1000) 202 | 203 | import scala.util.control.Breaks._ 204 | Future { 205 | breakable { 206 | atomic { implicit txn => 207 | for (n <- List(1, 2, 3)) { 208 | pop(lst, n) 209 | break 210 | } 211 | } 212 | } 213 | log(s"after removing - $lst") 214 | } 215 | Thread.sleep(1000) 216 | 217 | import scala.util.control._ 218 | Future { 219 | breakable { 220 | atomic.withControlFlowRecognizer { 221 | case c: ControlThrowable => false 222 | } { implicit txn => 223 | for (n <- List(1, 2, 3)) { 224 | pop(lst, n) 225 | break 226 | } 227 | } 228 | } 229 | log(s"after removing - $lst") 230 | } 231 | 232 | } 233 | 234 | 235 | object CompositionCatchingExceptions extends App { 236 | import scala.concurrent.stm._ 237 | import CompositionSortedList._ 238 | import CompositionExceptions.pop 239 | 240 | val lst = new TSortedList 241 | lst.insert(4) 242 | lst.insert(9) 243 | lst.insert(1) 244 | lst.insert(16) 245 | 246 | atomic { implicit txn => 247 | // note - a failed nested transaction executes the top-level `afterRollback` callback! 248 | // Txn.afterRollback { _ => log(s"afterRollback") } 249 | pop(lst, 2) 250 | log(s"lst = $lst") 251 | try { pop(lst, 3) } 252 | catch { case e: Exception => log(s"Houston... $e!") } 253 | pop(lst, 1) 254 | } 255 | 256 | log(s"result - $lst") 257 | } 258 | 259 | 260 | 261 | 262 | 263 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/ch7/Retry.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package ch7 3 | 4 | 5 | 6 | 7 | 8 | 9 | object RetryHeadWaitBad extends App { 10 | import scala.concurrent._ 11 | import ExecutionContext.Implicits.global 12 | import scala.concurrent.stm._ 13 | import CompositionSortedList._ 14 | 15 | def headWait(lst: TSortedList): Int = atomic { implicit txn => 16 | while (lst.head() == null) {} 17 | lst.head().elem 18 | } 19 | 20 | val myList = new TSortedList 21 | 22 | Future { 23 | val headElem = headWait(myList) 24 | log(s"The first element is $headElem") 25 | } 26 | Thread.sleep(1000) 27 | Future { myList.insert(1) } 28 | 29 | } 30 | 31 | 32 | object RetryHeadWait extends App { 33 | import scala.concurrent._ 34 | import ExecutionContext.Implicits.global 35 | import scala.concurrent.stm._ 36 | import CompositionSortedList._ 37 | 38 | def headWait(lst: TSortedList): Int = atomic { implicit txn => 39 | if (lst.head() != null) lst.head().elem 40 | else retry 41 | } 42 | 43 | val myList = new TSortedList 44 | 45 | Future { 46 | blocking { 47 | log(s"The first element is ${headWait(myList)}") 48 | } 49 | } 50 | Thread.sleep(1000) 51 | Future { myList.insert(1) } 52 | 53 | } 54 | 55 | 56 | object RetryChaining extends App { 57 | import scala.concurrent._ 58 | import ExecutionContext.Implicits.global 59 | import scala.concurrent.stm._ 60 | import CompositionSortedList._ 61 | import RetryHeadWait._ 62 | 63 | val queue1 = new TSortedList 64 | val queue2 = new TSortedList 65 | 66 | val consumer = Future { 67 | blocking { 68 | atomic { implicit txn => 69 | log(s"probe queue1") 70 | log(s"got: ${headWait(queue1)}") 71 | } orAtomic { implicit txn => 72 | log(s"probe list2") 73 | log(s"got: ${headWait(queue2)}") 74 | } 75 | } 76 | } 77 | Thread.sleep(50) 78 | Future { queue2.insert(2) } 79 | Thread.sleep(50) 80 | Future { queue1.insert(1) } 81 | 82 | } 83 | 84 | 85 | object RetryTimeouts extends App { 86 | import scala.concurrent._ 87 | import ExecutionContext.Implicits.global 88 | import scala.concurrent.stm._ 89 | 90 | val message = Ref("") 91 | 92 | Future { 93 | blocking { 94 | atomic.withRetryTimeout(1000) { implicit txn => 95 | if (message() != "") s"got a message - ${message()}" 96 | else retry 97 | } 98 | } 99 | } foreach { 100 | case msg => log(msg) 101 | } 102 | 103 | Thread.sleep(1025) 104 | 105 | atomic { implicit txn => 106 | message() = "Howdy!" 107 | } 108 | 109 | } 110 | 111 | 112 | object RetryFor extends App { 113 | import scala.concurrent._ 114 | import ExecutionContext.Implicits.global 115 | import scala.concurrent.stm._ 116 | 117 | val message = Ref("") 118 | 119 | Future { 120 | blocking { 121 | atomic { implicit txn => 122 | if (message() == "") { 123 | retryFor(1000) 124 | log(s"no message - '${message()}'") 125 | } else log(s"got a message - '${message()}'") 126 | } 127 | } 128 | } 129 | 130 | Thread.sleep(1025) 131 | 132 | message.single() = "Howdy!" 133 | 134 | } 135 | 136 | 137 | 138 | 139 | 140 | 141 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/ch7/TransactionalCollections.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package ch7 3 | 4 | 5 | 6 | 7 | 8 | 9 | object TransactionLocals extends App { 10 | import scala.concurrent._ 11 | import ExecutionContext.Implicits.global 12 | import scala.concurrent.stm._ 13 | import CompositionSortedList._ 14 | 15 | val myLog = TxnLocal("") 16 | 17 | def clearList(lst: TSortedList) = atomic { implicit txn => 18 | while (lst.head() != null) { 19 | myLog() = myLog() + "\nremoved " + lst.head().elem 20 | lst.head() = lst.head().next() 21 | } 22 | } 23 | 24 | val myList = new TSortedList().insert(14).insert(22) 25 | def clearWithLog(): String = atomic { implicit txn => 26 | clearList(myList) 27 | myLog() 28 | } 29 | val f = Future { clearWithLog() } 30 | val g = Future { clearWithLog() } 31 | for (h1 <- f; h2 <- g) { 32 | log(s"Log for f: $h1\nLog for g: $h2") 33 | } 34 | 35 | } 36 | 37 | 38 | object TransactionalArray extends App { 39 | import scala.concurrent._ 40 | import ExecutionContext.Implicits.global 41 | import scala.concurrent.stm._ 42 | 43 | val pages = Seq.fill(5)("Scala 2.10 is out, " * 7) 44 | val website = TArray(pages) 45 | 46 | def replace(pat: String, txt: String): Unit = atomic { implicit txn => 47 | for (i <- 0 until website.length) website(i) = website(i).replace(pat, txt) 48 | } 49 | 50 | def asString = atomic { implicit txn => 51 | var s: String = "" 52 | for (i <- 0 until website.length) s += s"Page $i\n=======\n${website(i)}\n\n" 53 | s 54 | } 55 | 56 | Future { replace("2.10", "2.11") } 57 | Thread.sleep(30) 58 | Future { replace("2.11", "2.12") } 59 | Thread.sleep(250) 60 | Future { log(s"Document\n$asString") } 61 | 62 | } 63 | 64 | 65 | object TransactionalMap extends App { 66 | import scala.concurrent._ 67 | import ExecutionContext.Implicits.global 68 | import scala.concurrent.stm._ 69 | 70 | val tmap = TMap("a" -> 1, "B" -> 2, "C" -> 3) 71 | 72 | Future { 73 | atomic { implicit txn => 74 | tmap("A") = 1 75 | tmap.remove("a") 76 | } 77 | } 78 | Thread.sleep(23) 79 | Future { 80 | val snap = tmap.single.snapshot 81 | log(s"atomic snapshot: $snap") 82 | } 83 | 84 | } 85 | 86 | 87 | object TransactionalDocument extends App { 88 | import scala.concurrent._ 89 | import ExecutionContext.Implicits.global 90 | import scala.concurrent.stm._ 91 | 92 | class TDocument(val name: String) { 93 | val pages = Ref(TArray.ofDim[String](0)) 94 | 95 | def addPage(page: String): this.type = atomic { implicit txn => 96 | val oarray = pages() 97 | val narray = TArray.ofDim[String](oarray.length + 1) 98 | for (i <- 0 until oarray.length) narray(i) = oarray(i) 99 | narray(oarray.length) = page 100 | pages() = narray 101 | this 102 | } 103 | 104 | override def toString = atomic { implicit txn => 105 | val array = pages() 106 | (0 until array.length).map(i => s"Page $i\n=======\n${array(i)}\n\n").mkString 107 | } 108 | } 109 | 110 | val doc = new TDocument("MyBook") 111 | doc.addPage("My Book Title") 112 | doc.addPage("Abstract: This is a book about concurrency.") 113 | 114 | Future { doc.addPage("Why is concurrency important.") } 115 | Future { doc.addPage("What concurrency means to you.") } 116 | Thread.sleep(250) 117 | Future { log(s"Document\n$doc") } 118 | 119 | } 120 | 121 | 122 | object TransactionalLibrary extends App { 123 | import scala.concurrent._ 124 | import ExecutionContext.Implicits.global 125 | import scala.concurrent.stm._ 126 | import TransactionalDocument._ 127 | 128 | class TLibrary { 129 | val documents = TMap.empty[String, TDocument] 130 | 131 | def addDoc(doc: TDocument): this.type = atomic { implicit txn => 132 | if (documents.contains(doc.name)) sys.error("Document ${doc.name} already exists!") 133 | else documents(doc.name) = doc 134 | this 135 | } 136 | 137 | def removeDoc(name: String): Option[TDocument] = documents.single.remove(name) 138 | 139 | def findDocs(pattern: String): List[String] = atomic { implicit txn => 140 | documents.filter({ 141 | case (name, doc) => 142 | val pages = doc.pages() 143 | (0 until pages.length).exists(i => pages(i).contains(pattern)) 144 | }).keys.toList 145 | } 146 | } 147 | 148 | val library = new TLibrary() 149 | library.addDoc(new TDocument("README.md").addPage("Attention - important!")) 150 | 151 | Future { 152 | library.addDoc(new TDocument("ElManual.md").addPage("Es muy importante!")) 153 | Thread.sleep(5) 154 | library.removeDoc("README.md") 155 | } 156 | Future { 157 | val matches = library.findDocs("important").mkString("\n") 158 | log(s"Matches:\n$matches") 159 | } 160 | 161 | } 162 | 163 | 164 | 165 | 166 | 167 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/ch8/Communicating.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package ch8 3 | 4 | 5 | 6 | import akka.actor._ 7 | import akka.event.Logging 8 | import akka.util.Timeout 9 | import akka.pattern.{ask, pipe, gracefulStop} 10 | import akka.util.Timeout 11 | import scala.concurrent.duration._ 12 | import scala.concurrent.ExecutionContext.Implicits.global 13 | import scala.util._ 14 | 15 | 16 | 17 | class Pongy extends Actor { 18 | val log = Logging(context.system, this) 19 | def receive = { 20 | case "ping" => 21 | log.info("Got a ping -- ponging back!") 22 | sender ! "pong" 23 | context.stop(self) 24 | } 25 | override def postStop() = log.info("pongy going down") 26 | } 27 | 28 | 29 | class Pingy extends Actor { 30 | def receive = { 31 | case pongyRef: ActorRef => 32 | implicit val timeout = Timeout(2 seconds) 33 | val future = pongyRef ? "ping" 34 | pipe(future) to sender 35 | } 36 | } 37 | 38 | 39 | class Master extends Actor { 40 | val log = Logging(context.system, this) 41 | val pingy = ourSystem.actorOf(Props[Pingy], "pingy") 42 | val pongy = ourSystem.actorOf(Props[Pongy], "pongy") 43 | def receive = { 44 | case "start" => 45 | pingy ! pongy 46 | case "pong" => 47 | log.info("got a pong back!") 48 | context.stop(self) 49 | } 50 | override def postStop() = log.info("master going down") 51 | } 52 | 53 | 54 | object CommunicatingAsk extends App { 55 | val masta = ourSystem.actorOf(Props[Master], "masta") 56 | masta ! "start" 57 | Thread.sleep(1000) 58 | ourSystem.shutdown() 59 | } 60 | 61 | 62 | class Router extends Actor { 63 | var i = 0 64 | val children = for (_ <- 0 until 4) yield context.actorOf(Props[StringPrinter]) 65 | def receive = { 66 | case "stop" => context.stop(self) 67 | case msg => 68 | children(i) forward msg 69 | i = (i + 1) % 4 70 | } 71 | } 72 | 73 | 74 | object CommunicatingRouter extends App { 75 | val router = ourSystem.actorOf(Props[Router], "router") 76 | router ! "Hi." 77 | router ! "I'm talking to you!" 78 | Thread.sleep(1000) 79 | router ! "stop" 80 | Thread.sleep(1000) 81 | ourSystem.shutdown() 82 | } 83 | 84 | 85 | object CommunicatingPoisonPill extends App { 86 | val masta = ourSystem.actorOf(Props[Master], "masta") 87 | masta ! akka.actor.PoisonPill 88 | Thread.sleep(1000) 89 | ourSystem.shutdown() 90 | } 91 | 92 | 93 | class GracefulPingy extends Actor { 94 | val pongy = context.actorOf(Props[Pongy], "pongy") 95 | context.watch(pongy) 96 | 97 | def receive = { 98 | case GracefulPingy.CustomShutdown => 99 | context.stop(pongy) 100 | case Terminated(`pongy`) => 101 | context.stop(self) 102 | } 103 | } 104 | 105 | 106 | object GracefulPingy { 107 | object CustomShutdown 108 | } 109 | 110 | 111 | object CommunicatingGracefulStop extends App { 112 | val grace = ourSystem.actorOf(Props[GracefulPingy], "grace") 113 | val stopped = gracefulStop(grace, 3.seconds, GracefulPingy.CustomShutdown) 114 | stopped onComplete { 115 | case Success(x) => 116 | log("graceful shutdown successful") 117 | ourSystem.shutdown() 118 | case Failure(t) => 119 | log("grace not stopped!") 120 | ourSystem.shutdown() 121 | } 122 | } 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/ch8/Remoting.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package ch8 3 | 4 | 5 | 6 | import akka.actor._ 7 | import akka.event.Logging 8 | import scala.collection._ 9 | 10 | 11 | 12 | object RemotingPongySystem extends App { 13 | val system = remotingSystem("PongyDimension", 24321) 14 | val pongy = system.actorOf(Props[Pongy], "pongy") 15 | Thread.sleep(15000) 16 | system.shutdown() 17 | } 18 | 19 | 20 | class Runner extends Actor { 21 | val log = Logging(context.system, this) 22 | val pingy = context.actorOf(Props[Pingy], "pingy") 23 | def receive = { 24 | case "start" => 25 | val path = context.actorSelection("akka.tcp://PongyDimension@127.0.0.1:24321/user/pongy") 26 | path ! Identify(0) 27 | case ActorIdentity(0, Some(ref)) => 28 | pingy ! ref 29 | case ActorIdentity(0, None) => 30 | log.info("Something's wrong -- no pongy anywhere!") 31 | context.stop(self) 32 | case "pong" => 33 | log.info("got a pong from another dimension.") 34 | context.stop(self) 35 | } 36 | } 37 | 38 | 39 | object RemotingPingySystem extends App { 40 | val system = remotingSystem("PingyDimension", 24567) 41 | val runner = system.actorOf(Props[Runner], "runner") 42 | runner ! "start" 43 | Thread.sleep(5000) 44 | system.shutdown() 45 | } 46 | 47 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/ch8/Supervision.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package ch8 3 | 4 | 5 | 6 | import akka.actor._ 7 | import akka.event.Logging 8 | import akka.pattern.pipe 9 | import akka.actor.SupervisorStrategy._ 10 | import org.apache.commons.io.FileUtils 11 | import scala.io.Source 12 | import scala.collection._ 13 | import scala.concurrent._ 14 | import scala.concurrent.ExecutionContext.Implicits.global 15 | import scala.concurrent.duration._ 16 | 17 | 18 | 19 | class Naughty extends Actor { 20 | val log = Logging(context.system, this) 21 | def receive = { 22 | case s: String => log.info(s) 23 | case msg => throw new RuntimeException 24 | } 25 | override def postRestart(t: Throwable) = log.info("naughty restarted") 26 | } 27 | 28 | 29 | class Supervisor extends Actor { 30 | val child = context.actorOf(Props[Naughty], "victim") 31 | def receive = PartialFunction.empty 32 | override val supervisorStrategy = 33 | OneForOneStrategy() { 34 | case ake: ActorKilledException => Restart 35 | case _ => Escalate 36 | } 37 | } 38 | 39 | 40 | object SupervisionKill extends App { 41 | val s = ourSystem.actorOf(Props[Supervisor], "super") 42 | ourSystem.actorSelection("/user/super/*") ! Kill 43 | ourSystem.actorSelection("/user/super/*") ! "sorry about that" 44 | ourSystem.actorSelection("/user/super/*") ! "kaboom".toList 45 | Thread.sleep(1000) 46 | ourSystem.stop(s) 47 | Thread.sleep(1000) 48 | ourSystem.shutdown() 49 | } 50 | 51 | 52 | class Downloader extends Actor { 53 | def receive = { 54 | case DownloadManager.Download(url, dest) => 55 | val content = Source.fromURL(url) 56 | FileUtils.write(new java.io.File(dest), content.mkString) 57 | sender ! DownloadManager.Finished(dest) 58 | } 59 | } 60 | 61 | 62 | class DownloadManager(val downloadSlots: Int) extends Actor { 63 | val log = Logging(context.system, this) 64 | val downloaders = mutable.Queue[ActorRef]() 65 | val pendingWork = mutable.Queue[DownloadManager.Download]() 66 | val workItems = mutable.Map[ActorRef, DownloadManager.Download]() 67 | 68 | override def preStart(): Unit = { 69 | for (i <- 0 until downloadSlots) downloaders.enqueue(context.actorOf(Props[Downloader], s"dl$i")) 70 | } 71 | 72 | private def checkMoreDownloads(): Unit = { 73 | if (pendingWork.nonEmpty && downloaders.nonEmpty) { 74 | val dl = downloaders.dequeue() 75 | val workItem = pendingWork.dequeue() 76 | log.info(s"$workItem starting, ${downloaders.size} download slots left") 77 | dl ! workItem 78 | workItems(dl) = workItem 79 | } 80 | } 81 | 82 | def receive = { 83 | case msg @ DownloadManager.Download(url, dest) => 84 | pendingWork.enqueue(msg) 85 | checkMoreDownloads() 86 | case DownloadManager.Finished(dest) => 87 | workItems.remove(sender) 88 | downloaders.enqueue(sender) 89 | log.info(s"Download to '$dest' finished, ${downloaders.size} download slots left") 90 | checkMoreDownloads() 91 | } 92 | 93 | override val supervisorStrategy = 94 | OneForOneStrategy(maxNrOfRetries = 6, withinTimeRange = 30 seconds) { 95 | case fnf: java.io.FileNotFoundException => 96 | log.info(s"Resource could not be found: $fnf") 97 | workItems.remove(sender) 98 | downloaders.enqueue(sender) 99 | Resume 100 | case _ => 101 | Escalate 102 | } 103 | } 104 | 105 | 106 | object DownloadManager { 107 | case class Download(url: String, dest: String) 108 | case class Finished(dest: String) 109 | } 110 | 111 | 112 | object SupervisionDownloader extends App { 113 | import DownloadManager._ 114 | val downloadManager = ourSystem.actorOf(Props(classOf[DownloadManager], 4), "manager") 115 | downloadManager ! Download("http://www.w3.org/Addressing/URL/url-spec.txt", "url-spec.txt") 116 | Thread.sleep(1000) 117 | downloadManager ! Download("https://github.com/scala/scala/blob/master/README.md", "README.md") 118 | Thread.sleep(5000) 119 | ourSystem.stop(downloadManager) 120 | Thread.sleep(5000) 121 | ourSystem.shutdown() 122 | } 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/ch8/package.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | 3 | 4 | 5 | import akka.actor.ActorSystem 6 | import com.typesafe.config._ 7 | 8 | 9 | 10 | package object ch8 { 11 | 12 | lazy val ourSystem = ActorSystem("OurExampleSystem") 13 | 14 | def remotingConfig(port: Int) = ConfigFactory.parseString(s""" 15 | akka { 16 | actor.provider = "akka.remote.RemoteActorRefProvider" 17 | remote { 18 | enabled-transports = ["akka.remote.netty.tcp"] 19 | netty.tcp { 20 | hostname = "127.0.0.1" 21 | port = $port 22 | } 23 | } 24 | } 25 | """) 26 | 27 | def remotingSystem(name: String, port: Int) = ActorSystem(name, remotingConfig(port)) 28 | 29 | } 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/ch9/Debugging.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package ch9 3 | 4 | 5 | 6 | 7 | 8 | 9 | object Deadlock extends App { 10 | 11 | class Account(var money: Int) 12 | 13 | def send(a: Account, b: Account, n: Int) = a.synchronized { 14 | b.synchronized { 15 | a.money -= n 16 | b.money += n 17 | } 18 | } 19 | 20 | val a = new Account(1000) 21 | val b = new Account(2000) 22 | val t1 = ch2.thread { for (i <- 0 until 100) send(a, b, 1) } 23 | val t2 = ch2.thread { for (i <- 0 until 100) send(b, a, 1) } 24 | t1.join() 25 | t2.join() 26 | 27 | } 28 | 29 | 30 | object Correctness extends App { 31 | import java.util.concurrent.atomic._ 32 | import scala.concurrent._ 33 | import ExecutionContext.Implicits.global 34 | import scala.annotation.tailrec 35 | 36 | class Accumulator[T](z: T)(op: (T, T) => T) { 37 | private val value = new AtomicReference(z) 38 | @tailrec final def add(v: T): Unit = { 39 | val ov = value.get 40 | val nv = op(ov, v) 41 | if (!value.compareAndSet(ov, nv)) add(v) 42 | } 43 | def apply() = value.get 44 | } 45 | 46 | class CountDownLatch(n: Int)(onDone: =>Unit) { 47 | private val left = new AtomicInteger(n) 48 | def count() = 49 | if (left.decrementAndGet() == 0) onDone 50 | } 51 | 52 | def fold[T](fs: Seq[Future[T]])(z: T)(op: (T, T) => T): Future[T] = { 53 | val p = Promise[T]() 54 | val accu = new Accumulator(z)(op) 55 | val latch = new CountDownLatch(fs.length)({ 56 | val total = accu() 57 | p.trySuccess(total) 58 | }) 59 | for (f <- fs) f foreach { case v => 60 | accu.add(v) 61 | latch.count() 62 | } 63 | p.future 64 | } 65 | 66 | val fs = for (i <- 0 until 5) yield Future { i } 67 | val folded = fold(fs)(0)(_ + _) 68 | folded foreach { case v => log(s"folded: $v") } 69 | 70 | } 71 | 72 | 73 | object Performance extends App { 74 | import java.util.concurrent.atomic._ 75 | import scala.annotation.tailrec 76 | import org.scalameter._ 77 | 78 | class Accumulator[T](z: T)(op: (T, T) => T) { 79 | private val value = new AtomicReference(z) 80 | @tailrec final def add(v: T): Unit = { 81 | val ov = value.get 82 | val nv = op(ov, v) 83 | if (!value.compareAndSet(ov, nv)) add(v) 84 | } 85 | def apply() = value.get 86 | } 87 | 88 | val time = measure { 89 | val acc = new Accumulator(0L)(_ + _) 90 | var i = 0 91 | val total = 1000000 92 | while (i < total) { 93 | acc.add(i) 94 | i += 1 95 | } 96 | } 97 | 98 | println("Running time: " + time) 99 | 100 | val accTime = config( 101 | Key.exec.minWarmupRuns -> 20, 102 | Key.exec.maxWarmupRuns -> 40, 103 | Key.exec.benchRuns -> 30, 104 | Key.verbose -> true 105 | ) withWarmer(new Warmer.Default) measure { 106 | val acc = new Accumulator(0L)(_ + _) 107 | var i = 0 108 | val total = 1000000 109 | while (i < total) { 110 | acc.add(i) 111 | i += 1 112 | } 113 | } 114 | 115 | println("Accumulator time: " + accTime) 116 | 117 | class LongAccumulator(z: Long)(op: (Long, Long) => Long) { 118 | private val value = new AtomicLong(z) 119 | @tailrec final def add(v: Long): Unit = { 120 | val ov = value.get 121 | val nv = op(ov, v) 122 | if (!value.compareAndSet(ov, nv)) add(v) 123 | } 124 | def apply() = value.get 125 | } 126 | 127 | val longAccTime = config( 128 | Key.exec.minWarmupRuns -> 20, 129 | Key.exec.maxWarmupRuns -> 40, 130 | Key.exec.benchRuns -> 30, 131 | Key.verbose -> true 132 | ) withWarmer(new Warmer.Default) measure { 133 | val acc = new LongAccumulator(0L)(_ + _) 134 | var i = 0 135 | val total = 1000000 136 | while (i < total) { 137 | acc.add(i) 138 | i += 1 139 | } 140 | } 141 | 142 | println("Long accumulator time: " + longAccTime) 143 | 144 | val longAccTime4 = config( 145 | Key.exec.minWarmupRuns -> 20, 146 | Key.exec.maxWarmupRuns -> 40, 147 | Key.exec.benchRuns -> 30, 148 | Key.verbose -> true 149 | ) withWarmer(new Warmer.Default) measure { 150 | val acc = new LongAccumulator(0L)(_ + _) 151 | val total = 1000000 152 | val p = 4 153 | val threads = for (j <- 0 until p) yield ch2.thread { 154 | val start = j * total / p 155 | var i = start 156 | while (i < start + total / p) { 157 | acc.add(i) 158 | i += 1 159 | } 160 | } 161 | for (t <- threads) t.join() 162 | } 163 | 164 | println("4 threads long accumulator time: " + longAccTime4) 165 | 166 | class ParLongAccumulator(z: Long)(op: (Long, Long) => Long) { 167 | private val par = Runtime.getRuntime.availableProcessors * 128 168 | private val values = new AtomicLongArray(par) 169 | @tailrec final def add(v: Long): Unit = { 170 | val id = Thread.currentThread.getId.toInt 171 | val i = math.abs(scala.util.hashing.byteswap32(id)) % par 172 | val ov = values.get(i) 173 | val nv = op(ov, v) 174 | if (!values.compareAndSet(i, ov, nv)) add(v) 175 | } 176 | def apply(): Long = { 177 | var total = 0L 178 | for (i <- 0 until values.length) total = op(total, values.get(i)) 179 | total 180 | } 181 | } 182 | 183 | val parLongAccTime = config( 184 | Key.exec.minWarmupRuns -> 100, 185 | Key.exec.maxWarmupRuns -> 200, 186 | Key.exec.benchRuns -> 80, 187 | Key.verbose -> true 188 | ) withWarmer(new Warmer.Default) measure { 189 | val acc = new ParLongAccumulator(0L)(_ + _) 190 | val total = 1000000 191 | val p = 4 192 | val threads = for (j <- 0 until p) yield ch2.thread { 193 | val start = j * total / p 194 | var i = start 195 | while (i < start + total / p) { 196 | acc.add(i) 197 | i += 1 198 | } 199 | } 200 | for (t <- threads) t.join() 201 | } 202 | 203 | println("Parallel long accumulator time: " + parLongAccTime) 204 | 205 | } 206 | 207 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/ch9/FTPServer.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package ch9 3 | 4 | 5 | 6 | import rx.lang.scala._ 7 | import scala.collection._ 8 | import scala.util.Try 9 | import scala.concurrent._ 10 | import scala.concurrent.ExecutionContext.Implicits.global 11 | import akka.actor._ 12 | import akka.pattern.pipe 13 | import akka.event.Logging 14 | import scala.concurrent.stm._ 15 | import java.io._ 16 | import org.apache.commons.io.FileUtils 17 | import org.apache.commons.io.filefilter.TrueFileFilter 18 | import scala.collection._ 19 | import scala.collection.convert.decorateAsScala._ 20 | 21 | 22 | 23 | class FileSystem(val rootpath: String) { 24 | val files = TMap[String, FileInfo]() 25 | 26 | def init() = atomic { implicit txn => 27 | files.clear() 28 | 29 | val rootDir = new File(rootpath) 30 | val all = TrueFileFilter.INSTANCE 31 | val fileIterator = FileUtils.iterateFilesAndDirs(rootDir, all, all).asScala 32 | for (file <- fileIterator) { 33 | val info = FileInfo(file) 34 | //if (info.isDir && info.path.contains("src")) println(info) 35 | files(info.path) = info 36 | } 37 | } 38 | 39 | def getFileList(dir: String): Map[String, FileInfo] = atomic { implicit txn => 40 | files.filter(_._2.parent == dir) 41 | } 42 | 43 | private def copyOnDisk(srcfile: File, destfile: File) = { 44 | FileUtils.copyFile(srcfile, destfile) 45 | atomic { implicit txn => 46 | val ninfo = files(srcfile.getPath) 47 | files(srcfile.getPath) = ninfo.copy(state = ninfo.state.dec) 48 | files(destfile.getPath) = FileInfo(destfile) 49 | } 50 | } 51 | 52 | def copyFile(srcpath: String, destpath: String): String = atomic { implicit txn => 53 | import FileSystem._ 54 | val srcfile = new File(srcpath) 55 | val destfile = new File(destpath) 56 | val info = files(srcpath) 57 | if (files.contains(destpath)) sys.error(s"Destination $destpath already exists.") 58 | info.state match { 59 | case Created => sys.error(s"File $srcpath being created.") 60 | case Deleted => sys.error(s"File $srcpath already deleted.") 61 | case Idle | Copying(_) => 62 | files(srcpath) = info.copy(state = info.state.inc) 63 | files(destpath) = FileInfo.creating(destfile, info.size) 64 | Txn.afterCommit { _ => copyOnDisk(srcfile, destfile) } 65 | srcpath 66 | } 67 | } 68 | 69 | def deleteFile(srcpath: String): String = atomic { implicit txn => 70 | import FileSystem._ 71 | val info = files(srcpath) 72 | info.state match { 73 | case Created => sys.error(s"File $srcpath not yet created.") 74 | case Copying(_) => sys.error(s"Cannot delete $srcpath, file being copied.") 75 | case Deleted => sys.error(s"File $srcpath already being deleted.") 76 | case Idle => 77 | files(srcpath) = info.copy(state = Deleted) 78 | Txn.afterCommit { _ => 79 | FileUtils.forceDelete(info.toFile) 80 | files.single.remove(srcpath) 81 | } 82 | srcpath 83 | } 84 | } 85 | 86 | def findFiles(regex: String): Seq[FileInfo] = { 87 | val snapshot = files.single.snapshot 88 | val infos = snapshot.values.toArray 89 | infos.par.filter(_.path.matches(regex)).seq 90 | } 91 | 92 | } 93 | 94 | 95 | object FileSystem { 96 | sealed trait State { 97 | def inc: State 98 | def dec: State 99 | } 100 | case object Created extends State { 101 | def inc = sys.error("File being created.") 102 | def dec = sys.error("File being created.") 103 | } 104 | case object Idle extends State { 105 | def inc = Copying(1) 106 | def dec = sys.error("Idle not copied.") 107 | } 108 | case class Copying(n: Int) extends State { 109 | def inc = Copying(n + 1) 110 | def dec = if (n > 1) Copying(n - 1) else Idle 111 | } 112 | case object Deleted extends State { 113 | def inc = sys.error("Cannot copy deleted.") 114 | def dec = sys.error("Deleted not copied") 115 | } 116 | } 117 | 118 | 119 | class FTPServerActor(fileSystem: FileSystem) extends Actor { 120 | import FTPServerActor._ 121 | 122 | val log = Logging(context.system, this) 123 | 124 | def receive = { 125 | case GetFileList(dir) => 126 | //println(fileSystem.files.snapshot.map(_._2).filter(_.isDir).filter(_.path.contains("src")).mkString("\n")) 127 | val filesMap = fileSystem.getFileList(dir) 128 | val files = filesMap.map(_._2).to[Seq] 129 | sender ! files 130 | case CopyFile(srcpath, destpath) => 131 | Future { 132 | Try(fileSystem.copyFile(srcpath, destpath)) 133 | } pipeTo sender 134 | case DeleteFile(path) => 135 | Future { 136 | Try(fileSystem.deleteFile(path)) 137 | } pipeTo sender 138 | case FindFiles(regex) => 139 | Future { 140 | Try(fileSystem.findFiles(regex)) 141 | } pipeTo sender 142 | } 143 | } 144 | 145 | 146 | object FTPServerActor { 147 | sealed trait Command 148 | case class GetFileList(dir: String) extends Command 149 | case class CopyFile(srcpath: String, destpath: String) extends Command 150 | case class DeleteFile(path: String) extends Command 151 | case class FindFiles(regex: String) extends Command 152 | 153 | def apply(fs: FileSystem) = Props(classOf[FTPServerActor], fs) 154 | } 155 | 156 | 157 | object FTPServer extends App { 158 | val fileSystem = new FileSystem(".") 159 | fileSystem.init() 160 | 161 | val port = args(0).toInt 162 | val actorSystem = ch8.remotingSystem("FTPServerSystem", port) 163 | val serverActor = actorSystem.actorOf(FTPServerActor(fileSystem), "server") 164 | val fileEventSubscription = fileSystemEvents(".").subscribe { event => 165 | event match { 166 | case FileCreated(path) => 167 | fileSystem.files.single(path) = FileInfo(new File(path)) 168 | case FileDeleted(path) => 169 | fileSystem.files.single.remove(path) 170 | case FileModified(path) => 171 | fileSystem.files.single(path) = FileInfo(new File(path)) 172 | } 173 | } 174 | } 175 | 176 | 177 | object FTPServerBench extends App { 178 | import org.scalameter._ 179 | 180 | val fileSystem = new FileSystem(".") 181 | fileSystem.init() 182 | 183 | val runningTime = config( 184 | Key.exec.minWarmupRuns -> 100, 185 | Key.exec.maxWarmupRuns -> 200, 186 | Key.exec.benchRuns -> 1000, 187 | Key.verbose -> true 188 | ) withWarmer(new Warmer.Default) measure { 189 | fileSystem.findFiles(".*ch5.*") 190 | } 191 | 192 | println("Running time: " + runningTime) 193 | 194 | } 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/ch9/package.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | 3 | 4 | 5 | import java.io._ 6 | import java.text.SimpleDateFormat 7 | import org.apache.commons.io.FileUtils 8 | import org.apache.commons.io.monitor._ 9 | import rx.lang.scala._ 10 | 11 | 12 | 13 | package ch9 { 14 | 15 | case class FileInfo(path: String, name: String, parent: String, modified: String, isDir: Boolean, size: Long, state: FileSystem.State) { 16 | def toRow = Array[AnyRef](name, if (isDir) "" else size / 1000 + "kB", modified) 17 | def toFile = new File(path) 18 | } 19 | 20 | object FileInfo { 21 | def apply(file: File): FileInfo = { 22 | val path = file.getPath 23 | val name = file.getName 24 | val parent = file.getParent 25 | val modified = dateFormat.format(file.lastModified) 26 | val isDir = file.isDirectory 27 | val size = if (isDir) -1 else FileUtils.sizeOf(file) 28 | FileInfo(path, name, parent, modified, isDir, size, FileSystem.Idle) 29 | } 30 | 31 | def creating(file: File, size: Long): FileInfo = { 32 | val path = file.getPath 33 | val name = file.getName 34 | val parent = file.getParent 35 | val modified = dateFormat.format(System.currentTimeMillis) 36 | val isDir = false 37 | FileInfo(path, name, parent, modified, isDir, size, FileSystem.Created) 38 | } 39 | } 40 | 41 | sealed trait FileEvent 42 | case class FileCreated(path: String) extends FileEvent 43 | case class FileDeleted(path: String) extends FileEvent 44 | case class FileModified(path: String) extends FileEvent 45 | 46 | } 47 | 48 | 49 | package object ch9 { 50 | 51 | val dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss") 52 | 53 | def fileSystemEvents(rootPath: String): Observable[FileEvent] = { 54 | Observable.apply { obs => 55 | val fileMonitor = new FileAlterationMonitor(1000) 56 | val fileObs = new FileAlterationObserver(rootPath) 57 | val fileLis = new FileAlterationListenerAdaptor { 58 | override def onFileCreate(file: File) = 59 | obs.onNext(FileCreated(file.getPath)) 60 | override def onFileChange(file: File) = 61 | obs.onNext(FileModified(file.getPath)) 62 | override def onFileDelete(file: File) = 63 | obs.onNext(FileDeleted(file.getPath)) 64 | override def onDirectoryCreate(file: File) = 65 | obs.onNext(FileCreated(file.getPath)) 66 | override def onDirectoryChange(file: File) = 67 | obs.onNext(FileModified(file.getPath)) 68 | override def onDirectoryDelete(file: File) = 69 | obs.onNext(FileDeleted(file.getPath)) 70 | } 71 | fileObs.addListener(fileLis) 72 | fileMonitor.addObserver(fileObs) 73 | fileMonitor.start() 74 | 75 | Subscription { fileMonitor.stop() } 76 | } 77 | } 78 | 79 | } 80 | 81 | 82 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/exercises/ch1/ex1.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package exercises 3 | package ch1 4 | 5 | object Ex1a extends App { 6 | 7 | def compose[A, B, C](g: B => C, f: A => B): A => C = x => g(f(x)) 8 | 9 | } 10 | 11 | 12 | object Ex1b extends App { 13 | 14 | def compose[A, B, C](g: B => C, f: A => B): A => C = g compose f 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/exercises/ch1/ex2.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package exercises 3 | package ch1 4 | 5 | object Ex2 extends App { 6 | 7 | def fuse[A, B](a: Option[A], b: Option[B]): Option[(A, B)] = for { 8 | aVal <- a 9 | bVal <- b 10 | } yield (aVal, bVal) 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/exercises/ch1/ex3.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package exercises 3 | package ch1 4 | 5 | object Ex3 extends App { 6 | 7 | def check2[T](xs: Seq[T])(pred: T => Boolean): Boolean = xs.forall { x => 8 | try { 9 | pred(x) 10 | } catch { 11 | case _: Exception => false 12 | } 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/exercises/ch1/ex4.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package exercises 3 | package ch1 4 | 5 | // for those who consider the same characters different elements 6 | object Ex4a extends App { 7 | 8 | def permutations(s: String): Seq[String] = { 9 | if (s.length == 0) Seq("") 10 | else for { 11 | i <- 0 until s.length 12 | q <- permutations(s.take(i) + s.takeRight(s.length - i - 1)) 13 | } yield s(i) + q 14 | } 15 | 16 | println(permutations("abba")) 17 | 18 | } 19 | 20 | 21 | // for those who consider the same characters the same elements 22 | object Ex4b extends App { 23 | 24 | def permutations(s: String): Seq[String] = { 25 | if (s.length == 0) Seq("") 26 | else { 27 | for { 28 | i <- s.map(c => s.indexOf(c)).toSet[Int].toSeq 29 | q <- permutations(s.take(i) + s.takeRight(s.length - i - 1)) 30 | } yield s(i) + q 31 | } 32 | } 33 | 34 | println(permutations("abba")) 35 | 36 | } 37 | 38 | 39 | // for those who in love with the standard library :) 40 | object Ex4c extends App { 41 | 42 | def permutations(x: String): Seq[String] = x.permutations.toList 43 | 44 | println(permutations("abba")) 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/exercises/ch2/ex1.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package exercises 3 | package ch2 4 | 5 | import org.learningconcurrency.ch2.thread 6 | 7 | object Ex1 extends App { 8 | 9 | def parallel[A, B](a: =>A, b: =>B): (A, B) = { 10 | var aVal: A = null.asInstanceOf[A] 11 | var bVal: B = null.asInstanceOf[B] 12 | 13 | val t1 = thread { 14 | aVal = a 15 | log(aVal.toString()) 16 | } 17 | 18 | val t2 = thread { 19 | bVal = b 20 | log(bVal.toString()) 21 | } 22 | 23 | t1.join() 24 | t2.join() 25 | 26 | (aVal, bVal) 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/exercises/ch2/ex10.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package exercises 3 | package ch2 4 | 5 | import scala.collection.mutable 6 | 7 | object Ex10 extends App { 8 | 9 | class PriorityTaskPool(val p:Int, val important: Int) { 10 | 11 | implicit val ord: Ordering[(Int,() => Unit)] = Ordering.by(_._1) 12 | 13 | private val tasks = mutable.PriorityQueue[(Int,() => Unit)]() 14 | 15 | @volatile 16 | private var terminated = false 17 | 18 | def asynchronous(priority: Int)(task: => Unit):Unit = tasks synchronized { 19 | tasks.enqueue((priority,() => task)) 20 | tasks.notify() 21 | } 22 | 23 | class Worker extends Thread { 24 | 25 | def poll() = tasks.synchronized { 26 | while (tasks.isEmpty) { 27 | tasks.wait() 28 | } 29 | log(s"queue: " + tasks.foldLeft("")((s,t)=>s"$s${t._1},")) 30 | tasks.dequeue() 31 | } 32 | 33 | override def run() = { 34 | while (true) { 35 | poll() match { 36 | case (p, task) if (p > important) || (!terminated) => task() 37 | case _ => 38 | } 39 | } 40 | } 41 | } 42 | 43 | def shutdown() = tasks.synchronized { 44 | terminated = true 45 | tasks.notify() 46 | } 47 | 48 | (1 to p).map((i) => new Worker()).map(_.start) 49 | 50 | } 51 | 52 | val tasks = new PriorityTaskPool(10, 300) 53 | 54 | (1 to 1000).foreach((i) => { 55 | val a = (Math.random*1000).toInt 56 | tasks.asynchronous(a)({log(s"<- $a")}) 57 | }) 58 | 59 | Thread.sleep(1) 60 | tasks.shutdown() 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/exercises/ch2/ex2.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package exercises 3 | package ch2 4 | 5 | object Ex2 extends App { 6 | 7 | def periodically(duration: Long)(f: () => Unit): Unit = { 8 | val worker = new Thread { 9 | while (true) { 10 | f() 11 | Thread.sleep(duration) 12 | } 13 | } 14 | 15 | worker.setName("Worker") 16 | worker.setDaemon(true) 17 | worker.start() 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/exercises/ch2/ex3.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package exercises 3 | package ch2 4 | 5 | object Ex3 extends App { 6 | 7 | class SyncVar[T] { 8 | 9 | private var empty:Boolean = true 10 | 11 | private var x:T = null.asInstanceOf[T] 12 | 13 | def get(): T = this.synchronized { 14 | if (empty) throw new Exception("must be non-empty") 15 | else { 16 | empty = true 17 | val v = x 18 | x = null.asInstanceOf[T] 19 | v 20 | } 21 | } 22 | 23 | def put(x: T):Unit = this.synchronized { 24 | if (!empty) throw new Exception("must be empty") 25 | else { 26 | empty = false 27 | this.x = x 28 | } 29 | } 30 | 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/exercises/ch2/ex4.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package exercises 3 | package ch2 4 | 5 | object Ex4 extends App { 6 | 7 | class SyncVar[T] { 8 | 9 | private var empty: Boolean = true 10 | 11 | private var x: T = null.asInstanceOf[T] 12 | 13 | def get(): T = this.synchronized { 14 | if (empty) throw new Exception("must be non-empty") 15 | else { 16 | empty = true 17 | x 18 | } 19 | } 20 | 21 | def put(x: T): Unit = this.synchronized { 22 | if (!empty) throw new Exception("must be empty") 23 | else { 24 | empty = false 25 | this.x = x 26 | } 27 | } 28 | 29 | def isEmpty = synchronized { 30 | empty 31 | } 32 | 33 | def nonEmpty = synchronized { 34 | !empty 35 | } 36 | 37 | } 38 | 39 | import org.learningconcurrency.ch2.thread 40 | 41 | val syncVar = new SyncVar[Int] 42 | 43 | val producer = thread { 44 | var x = 0 45 | while (x < 15) { 46 | if (syncVar.isEmpty) { 47 | syncVar.put(x) 48 | x = x + 1 49 | } 50 | 51 | } 52 | } 53 | 54 | val consumer = thread { 55 | var x = 0 56 | while (x != 15) { 57 | if (syncVar.nonEmpty) { 58 | log(s"get = ${syncVar.get}") 59 | x = x + 1 60 | } 61 | } 62 | } 63 | 64 | producer.join() 65 | consumer.join() 66 | 67 | 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/exercises/ch2/ex5.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package exercises 3 | package ch2 4 | 5 | object Ex5 extends App { 6 | 7 | class SyncVar[T] { 8 | 9 | private var empty: Boolean = true 10 | 11 | private var x: T = null.asInstanceOf[T] 12 | 13 | def isEmpty = synchronized { 14 | empty 15 | } 16 | 17 | def nonEmpty = synchronized { 18 | !empty 19 | } 20 | 21 | def getWait():T = this.synchronized { 22 | while (empty) 23 | this.wait() 24 | 25 | empty = true 26 | this.notify() 27 | x 28 | } 29 | 30 | def putWait(x: T): Unit = this.synchronized { 31 | while (!empty) 32 | this.wait() 33 | 34 | empty = false 35 | this.x = x 36 | this.notify() 37 | } 38 | 39 | 40 | } 41 | 42 | import org.learningconcurrency.ch2.thread 43 | 44 | val syncVar = new SyncVar[Int] 45 | 46 | val producer = thread { 47 | var x = 0 48 | while(x < 15) { 49 | syncVar.putWait(x) 50 | x = x + 1 51 | } 52 | } 53 | 54 | val consumer = thread { 55 | var x = -1 56 | while(x < 14) { 57 | x = syncVar.getWait 58 | log(s"get: $x") 59 | } 60 | } 61 | 62 | producer.join() 63 | consumer.join() 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/exercises/ch2/ex6.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package exercises 3 | package ch2 4 | 5 | import scala.collection.mutable._ 6 | 7 | 8 | object Ex6 extends App { 9 | 10 | class SyncQueue[T](val n:Int) { 11 | 12 | private var syncQueue = Queue[T]() 13 | 14 | def getWait():T = this.synchronized { 15 | while (syncQueue.isEmpty) { 16 | this.wait() 17 | } 18 | 19 | val x = syncQueue.dequeue 20 | this.notify() 21 | x 22 | } 23 | 24 | def putWait(x: T): Unit = this.synchronized { 25 | while (syncQueue.length == n) 26 | this.wait() 27 | 28 | syncQueue += x 29 | this.notify() 30 | } 31 | 32 | } 33 | 34 | import org.learningconcurrency.ch2.thread 35 | 36 | val syncVar = new SyncQueue[Int](10) 37 | 38 | val producer = thread { 39 | var x = 0 40 | while(x < 15) { 41 | syncVar.putWait(x) 42 | x = x + 1 43 | } 44 | } 45 | 46 | val consumer = thread { 47 | var x = -1 48 | while(x < 14) { 49 | x = syncVar.getWait() 50 | log(s"get: $x") 51 | } 52 | } 53 | 54 | producer.join() 55 | consumer.join() 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/exercises/ch2/ex7.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package exercises 3 | package ch2 4 | 5 | import org.learningconcurrency.ch2._ 6 | 7 | object Ex7 extends App { 8 | 9 | import SynchronizedProtectedUid._ 10 | 11 | class Account(val name: String, var money: Int) { 12 | val uid = getUniqueId() 13 | } 14 | 15 | def send(a1: Account, a2: Account, n: Int) { 16 | def adjust() { 17 | a1.money -= n 18 | a2.money += n 19 | } 20 | 21 | if (a1.uid < a2.uid) { 22 | a1.synchronized { 23 | a2.synchronized { 24 | adjust() 25 | } 26 | } 27 | } else { 28 | a2.synchronized { 29 | a1.synchronized { 30 | adjust() 31 | } 32 | } 33 | } 34 | } 35 | 36 | 37 | def sendAll(accounts: Set[Account], target: Account): Unit = { 38 | 39 | def adjust() = { 40 | target.money = accounts.foldLeft(0)((s, a) => { 41 | val money = a.money 42 | a.money = 0 43 | s + money 44 | } 45 | ) 46 | } 47 | 48 | def sendAllWithSynchronize(la: List[Account]): Unit = la match { 49 | case h :: t => h synchronized { 50 | sendAllWithSynchronize(t) 51 | } 52 | case _ => adjust() 53 | } 54 | 55 | sendAllWithSynchronize((target :: accounts.toList).sortBy(_.uid)) 56 | } 57 | 58 | val accounts = (1 to 100).map((i) => new Account(s"Account: $i",i*10)).toSet 59 | val target = new Account("Target account", 0) 60 | 61 | sendAll(accounts,target) 62 | 63 | accounts.foreach((a) => log(s"${a.name}, money = ${a.money}")) 64 | log(s"${target.name} - money = ${target.money}") 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/exercises/ch2/ex8.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package exercises 3 | package ch2 4 | 5 | 6 | import scala.collection.mutable 7 | 8 | object Ex8 extends App { 9 | 10 | class PriorityTaskPool { 11 | 12 | implicit val ord: Ordering[(Int,() => Unit)] = Ordering.by(_._1) 13 | 14 | private val tasks = mutable.PriorityQueue[(Int,() => Unit)]() 15 | 16 | def asynchronous(priority: Int)(task: => Unit):Unit = tasks synchronized { 17 | tasks.enqueue((priority,() => task)) 18 | tasks.notify() 19 | } 20 | 21 | 22 | object Worker extends Thread { 23 | 24 | setDaemon(true) 25 | 26 | def poll() = tasks.synchronized { 27 | while (tasks.isEmpty) { 28 | tasks.wait() 29 | } 30 | log("queue: " + tasks.foldLeft("")((s,t)=>s"$s${t._1},")) 31 | tasks.dequeue() 32 | } 33 | 34 | override def run() = { 35 | while (true) { 36 | poll() match { 37 | case (_, task) => task() 38 | } 39 | } 40 | } 41 | } 42 | 43 | Worker.start() 44 | 45 | } 46 | 47 | val tasks = new PriorityTaskPool 48 | 49 | (1 to 10).foreach((i) => { 50 | val a = (Math.random*1000).toInt 51 | tasks.asynchronous(a)({log(s"<- $a")}) 52 | }) 53 | 54 | Thread.sleep(10000) 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/exercises/ch2/ex9.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package exercises 3 | package ch2 4 | 5 | import org.learningconcurrency._ 6 | 7 | import scala.collection.mutable 8 | 9 | object Ex9 extends App { 10 | 11 | class PriorityTaskPool(val p:Int) { 12 | 13 | implicit val ord: Ordering[(Int,() => Unit)] = Ordering.by(_._1) 14 | 15 | private val tasks = mutable.PriorityQueue[(Int,() => Unit)]() 16 | 17 | def asynchronous(priority: Int)(task: => Unit):Unit = tasks synchronized { 18 | tasks.enqueue((priority,() => task)) 19 | tasks.notify() 20 | } 21 | 22 | class Worker extends Thread { 23 | 24 | setDaemon(true) 25 | 26 | def poll() = tasks.synchronized { 27 | while (tasks.isEmpty) { 28 | tasks.wait() 29 | } 30 | log("queue: " + tasks.foldLeft("")((s,t)=>s"$s${t._1},")) 31 | tasks.dequeue() 32 | } 33 | 34 | override def run() = { 35 | while (true) { 36 | poll() match { 37 | case (_, task) => task() 38 | } 39 | } 40 | } 41 | } 42 | 43 | (1 to p).map((i) => new Worker()).map(_.start) 44 | 45 | } 46 | 47 | val tasks = new PriorityTaskPool(10) 48 | 49 | (1 to 100).foreach((i) => { 50 | val a = (Math.random*1000).toInt 51 | tasks.asynchronous(a)({log(s"<- $a")}) 52 | }) 53 | 54 | Thread.sleep(10000) 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/exercises/ch3/ex1.scala: -------------------------------------------------------------------------------- 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 | } 50 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/exercises/ch3/ex2.scala: -------------------------------------------------------------------------------- 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 | } 68 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/exercises/ch3/ex3_4.scala: -------------------------------------------------------------------------------- 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 | prev = a 102 | length += 1 103 | } 104 | 105 | if (csl.iterator.length != length) throw new Exception(s"${csl.iterator.length} != $length") 106 | 107 | log(s"length = ${csl.iterator.length} ($length)") 108 | 109 | } -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/exercises/ch3/ex5.scala: -------------------------------------------------------------------------------- 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 | } 65 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/exercises/ch3/ex6.scala: -------------------------------------------------------------------------------- 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 | } 55 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/exercises/ch3/ex7.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package exercises 3 | package ch3 4 | 5 | import scala.collection._ 6 | 7 | /** 8 | * Implement a SyncConcurrentMap class that extends the Map interface from the scala.collection.concurrent package. 9 | * Use the synchronized statement to protect the state of the concurrent map. 10 | */ 11 | 12 | object Ex7 extends App { 13 | 14 | class SyncConcurrentMap[A, B] extends scala.collection.concurrent.Map[A, B] { 15 | 16 | private val m = mutable.Map.empty[A, B] 17 | 18 | override def putIfAbsent(k: A, v: B): Option[B] = m synchronized { 19 | m.get(k) match { 20 | case optV@Some(_) => optV 21 | case None => m.put(k, v) 22 | } 23 | } 24 | 25 | def replace(k: A, oldvalue: B, newvalue: B): Boolean = m synchronized { 26 | m.get(k) match { 27 | case Some(v) if ((v != null) && v.equals(oldvalue)) || ((v == null) && (oldvalue == null)) => m.put(k, newvalue); true 28 | case _ => false 29 | } 30 | } 31 | 32 | def remove(k: A, v: B): Boolean = m synchronized { 33 | m.get(k) match { 34 | case Some(oldvalue) if ((oldvalue != null) && oldvalue.equals(v)) || ((v == null) && (oldvalue == null)) => m.remove(k); true 35 | case _ => false 36 | } 37 | } 38 | 39 | override def replace(k: A, v: B): Option[B] = m synchronized { 40 | m.get(k) match { 41 | case old@Some(oldvalue) => m.put(k, v); old 42 | case None => None 43 | } 44 | 45 | } 46 | 47 | override def +=(kv: (A, B)): SyncConcurrentMap.this.type = m synchronized { 48 | m.put(kv._1, kv._2) 49 | this 50 | } 51 | 52 | override def -=(key: A): SyncConcurrentMap.this.type = m synchronized { 53 | m.remove(key) 54 | this 55 | } 56 | 57 | override def get(key: A): Option[B] = m synchronized { 58 | m.get(key) 59 | } 60 | 61 | override def iterator: scala.Iterator[(A, B)] = m synchronized { 62 | m.iterator 63 | } 64 | } 65 | 66 | val m = new SyncConcurrentMap[Int, String]() 67 | 68 | 69 | import org.learningconcurrency.ch2.thread 70 | 71 | val t = (1 to 100).map((i) => thread { 72 | (1 to 100).foreach { 73 | (k) => { 74 | val v = s"${Thread.currentThread().getName}" 75 | m.put(k, v) 76 | log(s"-> ($k,$v)") 77 | } 78 | } 79 | }) 80 | 81 | Thread.sleep(100) 82 | 83 | for ((k, v) <- m) { 84 | log(s"<- ($k,$v)") 85 | } 86 | 87 | t.foreach(_.join) 88 | 89 | } 90 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/exercises/ch3/ex8.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package exercises 3 | package ch3 4 | 5 | import java.io.File 6 | import java.io.FileInputStream 7 | import java.io.FileOutputStream 8 | import java.io.ObjectInputStream 9 | import java.io.ObjectOutputStream 10 | import java.util.regex.Pattern 11 | 12 | import scala.sys.process._ 13 | 14 | /** 15 | * Implement a method spawn that, given a block of Scala code, starts a new JVM process and runs the specified block in the new process: 16 | * def spawn[T](block: =>T): T = ??? 17 | * Once the block returns a value, the spawn method should return the value from the child process. 18 | * If the block throws an exception, the spawn method should throw the same exception. 19 | */ 20 | object Ex8 extends App { 21 | 22 | // This method's preconditions are the following: 23 | // - In case of executing in sbt, set `fork` setting to `true` (set fork := true ). 24 | // 25 | // If passed block which contains `System.exit`, this method throws `SecurityException`. 26 | def spawn[T](block: => T): T = { 27 | val className = Ex8_EvaluationApp.getClass().getName().split((Pattern.quote("$")))(0) 28 | val tmp = File.createTempFile("concurrent-programming-in-scala", null) 29 | tmp.deleteOnExit() 30 | 31 | val out = new ObjectOutputStream(new FileOutputStream(tmp)) 32 | try { 33 | out.writeObject(() => block) 34 | } finally { 35 | out.close() 36 | } 37 | 38 | val ret = Process(s"java -cp ${System.getProperty("java.class.path")} $className ${tmp.getCanonicalPath}").! 39 | if (ret != 0) 40 | throw new RuntimeException("fails to evaluate block in a new JVM process") 41 | 42 | val in = new ObjectInputStream(new FileInputStream(tmp)) 43 | try { 44 | in.readObject() match { 45 | case e: Throwable => throw e 46 | case x => x.asInstanceOf[T] 47 | } 48 | } finally { 49 | in.close() 50 | tmp.delete() 51 | } 52 | } 53 | 54 | val s1 = spawn({ 55 | 1 + 1 56 | }) 57 | assert(s1 == 2) 58 | 59 | try { 60 | spawn({ 61 | "test".toInt 62 | }) 63 | } catch { 64 | case e: NumberFormatException => 65 | case _: Throwable => assert(false) 66 | } 67 | 68 | try { 69 | spawn({ 70 | System.exit(0) 71 | }) 72 | } catch { 73 | case e: SecurityException => 74 | case _: Throwable => assert(false) 75 | } 76 | } -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/exercises/ch3/ex8_evaluationapp.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package exercises 3 | package ch3 4 | 5 | import java.io.FileInputStream 6 | import java.io.FileOutputStream 7 | import java.io.ObjectInputStream 8 | import java.io.ObjectOutputStream 9 | import java.security.Permission 10 | 11 | // This application receives the file path in which the serialized `Function0` object has been written. 12 | // Then, it reads and evaluates serialized `Function0` object, finally overwrites its result to the same file. 13 | object Ex8_EvaluationApp extends App { 14 | System.setSecurityManager(new SecurityManager() { 15 | // allows access to file. 16 | override def checkPermission(perm: Permission): Unit = {} 17 | // detects `System.exit` in order not to be halted by the caller. 18 | override def checkExit(status: Int): Unit = { 19 | throw new SecurityException("not allowed to pass a block which contains System.exit(int) !") 20 | } 21 | }); 22 | 23 | val path = args(0) 24 | 25 | val in = new ObjectInputStream(new FileInputStream(path)) 26 | try { 27 | val f0 = in.readObject().asInstanceOf[Function0[Any]] 28 | in.close() 29 | 30 | val out = new ObjectOutputStream(new FileOutputStream(path)) 31 | try { 32 | out.writeObject(f0()) 33 | } catch { 34 | case e: Throwable => out.writeObject(e) 35 | } finally { 36 | out.close() 37 | } 38 | } finally { 39 | in.close() 40 | } 41 | } -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/exercises/ch4/ex1.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package exercises 3 | package ch4 4 | 5 | import scala.concurrent.duration.Duration 6 | import scala.util.{Failure, Success} 7 | 8 | /** 9 | * Implement a command-line program that asks the user to input a URL of some website, 10 | * and displays the HTML of that website. 11 | * 12 | * Between the time that the user hits ENTER and the time that the HTML is retrieved, 13 | * the program should repetitively print a . to the standard output every 50 milliseconds, 14 | * with a two seconds timeout. 15 | * 16 | * Use only futures and promises, and avoid the synchronization primitives from the previous chapters. 17 | * You may reuse the timeout method defined in this chapter. 18 | */ 19 | 20 | object Ex1 extends App { 21 | 22 | import java.util._ 23 | 24 | import scala.concurrent._ 25 | import ExecutionContext.Implicits.global 26 | import scala.io.Source 27 | 28 | private val timer = new Timer(true) 29 | 30 | def stopTimer(t:Timer) = { 31 | t.cancel() 32 | t.purge() 33 | } 34 | 35 | def timeout(p: Promise[String], t: Long): Unit = { 36 | timer.schedule( 37 | new TimerTask { 38 | def run() = { 39 | p trySuccess (s"Sorry, timed out ($t ms)") 40 | } 41 | }, t 42 | ) 43 | } 44 | 45 | def timeOutPrinter(t:Timer): Unit = { 46 | t.schedule( 47 | new TimerTask { 48 | override def run(): Unit = print(".") 49 | },0,50 50 | ) 51 | } 52 | 53 | while (true) { 54 | println("---------------------------------------------") 55 | println("Please, input URL") 56 | val url = scala.io.StdIn.readLine 57 | 58 | val dotPrinterTimer = new Timer(true) 59 | 60 | val p = Promise[String] 61 | 62 | val reader = Future { 63 | timeout(p, 2000) 64 | Source.fromURL(url).mkString 65 | } onComplete { 66 | case Success(s) => p.trySuccess(s) 67 | case Failure(e) => p.trySuccess(s"Error !!!! ${e.toString}") 68 | } 69 | 70 | val printer = Future { 71 | println(s"Reading from $url, please wait ") 72 | timeOutPrinter(dotPrinterTimer) 73 | } 74 | 75 | val l = Await.result(p.future, Duration.Inf) 76 | 77 | stopTimer(dotPrinterTimer) 78 | println("") 79 | println(l) 80 | 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/exercises/ch4/ex2.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package exercises 3 | package ch4 4 | 5 | import scala.concurrent.duration.Duration 6 | import scala.concurrent.{Await, Promise} 7 | import scala.util.Try 8 | 9 | /** 10 | * Implement an abstraction called a single-assignment variable, represented by the IVar class: 11 | * 12 | * class IVar[T] { 13 | * def apply(): T = ??? 14 | * def :=(x: T): Unit = ??? 15 | * } 16 | * 17 | * When created, the IVar class does not contain a value, and calling apply results in an exception. 18 | * After a value is assigned using the := method, subsequent calls to := throw an exception, 19 | * and the apply method returns the previously assigned value. 20 | * 21 | * Use only futures and promises, and avoid the synchronization primitives from the previous chapters. 22 | * 23 | */ 24 | 25 | object Ex2 extends App { 26 | 27 | class IVar[T] { 28 | 29 | val p = Promise[T] 30 | 31 | def apply(): T = 32 | if (p.isCompleted) Await.result(p.future, Duration.Inf) 33 | else throw new Exception("Not contain a value") 34 | 35 | def :=(x: T): Unit = if (!p.tryComplete(Try(x))) throw new Exception("Value is already assigned") 36 | } 37 | 38 | import org.learningconcurrency.ch2.thread 39 | 40 | val v = new IVar[String] 41 | (1 to 10).foreach((i) => thread { 42 | try { 43 | v := s"v = ${Thread.currentThread().getName}" 44 | } catch { 45 | case e:Throwable => log(s"Error !!! ${e.getMessage}. Current value = ${v.apply}") 46 | } 47 | } 48 | ) 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/exercises/ch4/ex3.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package exercises 3 | package ch4 4 | 5 | /** 6 | * Extend the Future[T] type with the exists method, which takes a predicate and returns a Future[Boolean] object: 7 | * 8 | * def exists(p: T => Boolean): Future[Boolean] 9 | * 10 | * The resulting future is completed with true if and only if the original future is completed 11 | * and the predicate returns true, and false otherwise. 12 | * You can use future combinators, but you are not allowed to create any Promise objects in the implementation. 13 | * 14 | * The existing Future combinators to actually fail the resulting future when the predicate p throws an exception 15 | */ 16 | 17 | object Ex3 extends App { 18 | 19 | import scala.concurrent.{ExecutionContext, Future} 20 | import ExecutionContext.Implicits.global 21 | 22 | implicit class FutureOps[T](val self: Future[T]) { 23 | def exists(p: T => Boolean): Future[Boolean] = self.map(p) 24 | } 25 | 26 | //test 27 | val f1 = Future { 28 | 100 29 | } 30 | val f2 = Future { 31 | -100 32 | } 33 | val f3 = Future { 34 | throw new Exception("Error in predicate") 35 | } 36 | 37 | def p(i: Int) = i > 0 38 | 39 | import scala.concurrent.Await 40 | import scala.concurrent.duration.Duration 41 | 42 | log("f1 = " + Await.result(f1.exists(p), Duration.Inf).toString) 43 | log("f2 = " + Await.result(f2.exists(p), Duration.Inf).toString) 44 | 45 | f3.failed foreach { 46 | case t:Throwable => log(t.getMessage) 47 | } 48 | 49 | //log("f3 = " + Await.result(f3.exists(p), Duration(5,"sec")).toString) 50 | } 51 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/exercises/ch4/ex4.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package exercises 3 | package ch4 4 | 5 | 6 | /** 7 | * Repeat the previous exercise, but use Promise objects instead of future combinators. 8 | */ 9 | 10 | object Ex4 extends App { 11 | 12 | import scala.concurrent.{ExecutionContext, Future, Promise} 13 | import ExecutionContext.Implicits.global 14 | 15 | implicit class FutureOps[T](val self: Future[T]) { 16 | 17 | def exists(p: T => Boolean): Future[Boolean] = { 18 | 19 | val pr = Promise[Boolean] 20 | self foreach ((t: T) => pr.success(p(t))) 21 | self.failed foreach (_ => pr.success(false)) 22 | pr.future 23 | } 24 | } 25 | 26 | //test 27 | 28 | import scala.concurrent.duration.Duration 29 | 30 | val f1 = Future { 31 | 100 32 | } 33 | val f2 = Future { 34 | -100 35 | } 36 | val f3 = Future { 37 | throw new Exception("Error") 38 | } 39 | 40 | def p(i: Int) = i > 0 41 | 42 | import scala.concurrent.Await 43 | 44 | log("f1 = " + Await.result(f1.exists(p), Duration.Inf).toString) 45 | log("f2 = " + Await.result(f2.exists(p), Duration.Inf).toString) 46 | log("f3 = " + Await.result(f3.exists(p), Duration.Inf).toString) 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/exercises/ch4/ex5.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package exercises 3 | package ch4 4 | 5 | 6 | /** 7 | * Repeat the previous exercise, but use the Scala Async framework. 8 | */ 9 | 10 | object Ex5 extends App { 11 | 12 | import scala.async.Async.{async, await} 13 | import scala.concurrent.{ExecutionContext, Future} 14 | import ExecutionContext.Implicits.global 15 | 16 | implicit class FutureOps[T](val self: Future[T]) { 17 | def exists(p: T => Boolean): Future[Boolean] = 18 | async { 19 | val v = await { 20 | self 21 | } 22 | p(v) 23 | }.recover { case _ => false } 24 | } 25 | 26 | //test 27 | val f1 = Future { 28 | 100 29 | } 30 | val f2 = Future { 31 | -100 32 | } 33 | val f3 = Future { 34 | throw new Exception("Error") 35 | } 36 | 37 | def p(i: Int) = i > 0 38 | 39 | import scala.concurrent.Await 40 | import scala.concurrent.duration.Duration 41 | 42 | log("f1 = " + Await.result(f1.exists(p), Duration.Inf).toString) 43 | log("f2 = " + Await.result(f2.exists(p), Duration.Inf).toString) 44 | log("f3 = " + Await.result(f3.exists(p), Duration.Inf).toString) 45 | 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/exercises/ch4/ex6.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package exercises 3 | package ch4 4 | 5 | import scala.collection.immutable.IndexedSeq 6 | import scala.concurrent._ 7 | import scala.util.{Failure, Success, Try} 8 | 9 | 10 | object Ex6 extends App { 11 | 12 | /** 13 | * Implement the spawn method, which takes a command-line string, 14 | * asynchronously executes it as a child process, 15 | * and returns a future with the exit code of the child process: 16 | * 17 | * def spawn(command: String): Future[Int] 18 | * 19 | * Make sure that your implementation does not cause thread starvation. 20 | */ 21 | 22 | import ExecutionContext.Implicits.global 23 | import scala.concurrent.{Future, Promise} 24 | import scala.sys.process._ 25 | 26 | def spawn(command: String): Future[Int] = { 27 | 28 | val p = Promise[Int] 29 | 30 | Future { 31 | blocking { 32 | p.complete(Try(command !)) 33 | } 34 | } 35 | 36 | p.future 37 | } 38 | 39 | 40 | val f = for (i <- 1 to 100) yield spawn("ping -c 10 google.com") 41 | 42 | f.foreach((p) => p.onComplete{ 43 | case Success(i) => log(s"result = $i") 44 | case Failure(e) => log(s"Error !!!! ${e.toString}") 45 | } 46 | ) 47 | 48 | Thread.sleep(10000) 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/exercises/ch4/ex7.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package exercises 3 | package ch4 4 | 5 | 6 | /** 7 | * Implement the IMap class, which represents a single-assignment map: 8 | * 9 | * class IMap[K, V] { 10 | * def update(k: K, v: V): Unit 11 | * def apply(k: K): Future[V] 12 | * } 13 | * 14 | * Pairs of keys and values can be added to the IMap object, 15 | * but they can never be removed or modified. 16 | * 17 | * A specific key can be assigned only once, 18 | * and subsequent calls to update with that key results in an exception. 19 | * 20 | * Calling apply with a specific key returns a future, 21 | * which is completed after that key is inserted into the map. 22 | * 23 | * In addition to futures and promises, you may use the scala.collection.concurrent.Map class. 24 | */ 25 | 26 | import java.util.concurrent.ConcurrentHashMap 27 | 28 | import scala.concurrent.duration.Duration 29 | import scala.concurrent.{Await, Future, Promise} 30 | 31 | object Ex7 extends App { 32 | 33 | 34 | class IMap[K, V] { 35 | 36 | import scala.collection.convert.decorateAsScala._ 37 | 38 | private val m = new ConcurrentHashMap[K, Promise[V]]().asScala 39 | 40 | private def createPromise(v: V) = { 41 | val p = Promise[V] 42 | p.success(v) 43 | p 44 | } 45 | 46 | private def createEmptyPromise(k: K): Promise[V] = { 47 | val p = Promise[V] 48 | m.putIfAbsent(k, p) match { 49 | case Some(old) => old 50 | case None => p 51 | } 52 | } 53 | 54 | def update(k: K, v: V): Unit = { 55 | m.putIfAbsent(k, createPromise(v)) match { 56 | case Some(p) => 57 | try { 58 | p.success(v) 59 | } catch { 60 | case e:IllegalStateException => throw new Exception("A specific key can be assigned only once") 61 | case e => throw e 62 | } 63 | case None => 64 | } 65 | } 66 | 67 | def apply(k: K): Future[V] = { 68 | m.get(k) match { 69 | case Some(p) => p.future 70 | case None => createEmptyPromise(k).future 71 | } 72 | } 73 | 74 | } 75 | 76 | 77 | //test 78 | 79 | import org.learningconcurrency.ch2._ 80 | 81 | val m = new IMap[Int, String]() 82 | 83 | (1 to 100).map((i) => thread { 84 | m.update(1, Thread.currentThread().getName) 85 | }) 86 | 87 | (1 to 100).map((i) => thread { 88 | val l = Await.result(m(1), Duration.Inf) 89 | log(l) 90 | }) 91 | 92 | } 93 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/exercises/ch4/ex8.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package exercises 3 | package ch4 4 | 5 | /** 6 | * Extend the Promise[T] type with the compose method, 7 | * which takes a function of the S => T type, and returns a Promise[S] object: 8 | * 9 | * def compose[S](f: S => T): Promise[S] 10 | * 11 | * Whenever the resulting promise is completed with some value x of the type S (or failed), 12 | * the original promise must be completed with the value f(x) asynchronously (or failed), 13 | * unless the original promise is already completed. 14 | * 15 | */ 16 | 17 | object Ex8 extends App { 18 | 19 | import scala.concurrent.{ExecutionContext, Future, Promise} 20 | import ExecutionContext.Implicits.global 21 | import scala.util.{Failure, Success} 22 | 23 | implicit class PromiseOps[T](val self: Promise[T]) { 24 | 25 | def compose[S](f: S => T): Promise[S] = { 26 | 27 | val ps = Promise[S] 28 | 29 | ps.future.onComplete { 30 | case Success(s) => Future(self.trySuccess(f(s))) 31 | case Failure(e) => self.tryFailure(e) 32 | } 33 | 34 | ps 35 | } 36 | } 37 | 38 | //test 39 | val pT = Promise[String] 40 | val pS: Promise[Int] = pT.compose((s) => s"val = $s") 41 | 42 | Future { 43 | Thread.sleep(1000) 44 | pS.success(1) 45 | // pS.failure(new Exception) 46 | } 47 | 48 | 49 | 50 | pT.future foreach { 51 | case s => log(s) 52 | } 53 | 54 | pT.future.failed foreach { case t => log(s"q failed with $t") } 55 | 56 | 57 | Thread.sleep(2000) 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/exercises/ch5/Ex2.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package exercises 3 | package ch5 4 | 5 | import scala.util.Random 6 | 7 | /** 8 | * Count the occurrences of the whitespace character in a randomly generated string, 9 | * where the probability of a whitespace at each position is determined by a p parameter. 10 | * 11 | * Use the parallel foreach method. 12 | * 13 | * Plot a graph that correlates the running time of this operation with the p parameter. 14 | */ 15 | object Ex2 extends App { 16 | 17 | import org.learningconcurrency.ch5._ 18 | 19 | var r = new Random 20 | 21 | val chars = ('a' to 'z') ++ ('A' to 'Z') 22 | 23 | def generateSymbol(p: Double): Char = 24 | if (r.nextDouble() > p) chars(Random.nextInt(chars.length)) else ' ' 25 | 26 | def generateString(p: Double, length: Int = 10000): Seq[Char] = { 27 | (0 to length).map((i) => generateSymbol(p)) 28 | } 29 | 30 | def timedForeach(s: Seq[Char]) = { 31 | var count = 0 32 | def add = synchronized { 33 | count += 1 34 | } 35 | 36 | warmedTimed(times = 400) { 37 | s.par.foreach((s) => if (s == ' ') add) 38 | } 39 | } 40 | 41 | def timedCount(s: Seq[Char]) = { 42 | warmedTimed(times = 400) { 43 | s.par.count(_ == ' ') 44 | } 45 | } 46 | 47 | //probability 48 | val p = (0 until 10).map { i => i / 9.0 } 49 | 50 | log("---- Calculation occurrences with foreach method") 51 | val dataForeach = p.map((p) => (p, generateString(p))).map { 52 | case (p, s) => log(s"p = $p"); (p, timedForeach(s)) 53 | } 54 | 55 | log("---- Calculation occurrences with count method") 56 | val dataCount = p.map((p) => (p, generateString(p))).map { 57 | case (p, s) => log(s"p = $p"); (p, timedCount(s)) 58 | } 59 | 60 | //plot graph 61 | //uses https://github.com/quantifind/wisp 62 | 63 | import com.quantifind.charts.Highcharts._ 64 | 65 | hold 66 | line(dataForeach) 67 | line(dataCount) 68 | title("Ch5 Ex2") 69 | legend(Seq("foreach method", "count method")) 70 | xAxis("probability") 71 | yAxis("time (ms)") 72 | } 73 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/exercises/ch5/ex1.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package exercises 3 | package ch5 4 | 5 | /** 6 | * Measure the average running time of allocating a simple object on the JVM. 7 | */ 8 | 9 | object Ex1 extends App { 10 | 11 | object Timed { 12 | 13 | @volatile 14 | var dummy: Any = _ 15 | 16 | def buildObjects(count:Int) = { 17 | var i = 0 18 | val start = System.nanoTime 19 | while (i < count) { 20 | dummy = new Object 21 | i += 1 22 | } 23 | (System.nanoTime - start)/count.toDouble 24 | } 25 | 26 | } 27 | 28 | var i = 0 29 | var summ = 0D 30 | 31 | var timePrev = 0D 32 | while (i < 30) { 33 | 34 | val time = Timed.buildObjects(10000000) 35 | val e = Math.abs(time - timePrev)/time*100 36 | 37 | //check steady state 38 | if (e < 10) { 39 | i += 1 40 | summ += time 41 | } else { 42 | i = 0 43 | summ = time 44 | } 45 | 46 | timePrev = time 47 | log(s"time = ${time.toString} e = ${Math.round(e)}, i = $i") 48 | 49 | } 50 | 51 | log("----------------------------------------------------") 52 | log(s"avg = ${summ/(i+1)} nanoseconds") 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/exercises/ch6/Ex1.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package exercises 3 | package ch6 4 | 5 | /** 6 | * Implement a custom Observable[Thread] object that emits an event when it detects that a thread was started. 7 | * The implementation is allowed to miss some of the events. 8 | */ 9 | 10 | import java.util.Calendar 11 | 12 | import rx.lang.scala.Observable 13 | 14 | import scala.annotation.tailrec 15 | import scala.concurrent.duration._ 16 | 17 | object Ex1 extends App { 18 | 19 | val rootThreadGroup = getRootThread(Thread.currentThread.getThreadGroup) 20 | 21 | var existsThreads = Set.empty[Thread] 22 | 23 | @tailrec 24 | def getRootThread(t: ThreadGroup):ThreadGroup = { 25 | val parent = t.getParent 26 | if (parent == null) t else getRootThread(parent) 27 | } 28 | 29 | def getCurrentThreads = { 30 | val threads = new Array[Thread](rootThreadGroup.activeCount()) 31 | rootThreadGroup.enumerate(threads,true) 32 | 33 | threads.filter(_ != null) 34 | } 35 | 36 | def getNewThreads = { 37 | val currentThreads = getCurrentThreads 38 | val newThreads = currentThreads.filter(!existsThreads.contains(_)) 39 | 40 | //save threads 41 | existsThreads = currentThreads.toSet 42 | 43 | newThreads 44 | } 45 | 46 | def createObservableNewThreads: Observable[Thread] = { 47 | Observable[Thread] { 48 | (s) => { 49 | getNewThreads.foreach(s.onNext _) 50 | } 51 | } 52 | } 53 | 54 | //create Observable 55 | val o = for { 56 | _ <- Observable.interval(1 seconds) 57 | j <- createObservableNewThreads 58 | } yield j 59 | 60 | o.subscribe((t) => log(s"${Calendar.getInstance().getTime()}: ${t.toString}")) 61 | 62 | //test 63 | 64 | def createTestThread(name:String): Unit = { 65 | val t = new Thread(name) { 66 | override def run(): Unit = { 67 | Thread.sleep(5000) 68 | } 69 | } 70 | t.start() 71 | } 72 | 73 | Thread.sleep(2000) 74 | createTestThread("A") 75 | Thread.sleep(3000) 76 | createTestThread("B") 77 | 78 | Thread.sleep(10000) 79 | } 80 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/exercises/ch6/Ex2.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package exercises 3 | package ch6 4 | 5 | /** 6 | * Implement an Observable object that emits an event every 5 seconds and every 12 seconds, 7 | * but not if the elapsed time is a multiple of 30 seconds. 8 | * Use functional combinators on Observable objects. 9 | */ 10 | 11 | import rx.lang.scala.Observable 12 | import scala.concurrent.duration._ 13 | 14 | object Ex2A extends App { 15 | 16 | val a = Observable.interval(5 seconds).map(_ * 5) 17 | val b = Observable.interval(12 seconds).map(_ * 12) 18 | 19 | val c = (a merge b distinct) filter (_ % 30 != 0) 20 | 21 | c.subscribe((s) => log(s.toString)) 22 | 23 | Thread.sleep(70000) 24 | } 25 | 26 | object Ex2B extends App { 27 | 28 | val d = Observable.interval(1 seconds).filter((l) => (l % 30 != 0) && ((l % 5 == 0) || (l % 12 == 0))) 29 | d.subscribe((s) => log(s.toString)) 30 | 31 | Thread.sleep(70000) 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/exercises/ch6/Ex3.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package exercises 3 | package ch6 4 | 5 | import rx.lang.scala._ 6 | 7 | import scala.annotation.tailrec 8 | import scala.util.Random 9 | import scala.concurrent.duration._ 10 | 11 | /** 12 | * Use the randomQuote method from this section in order to create an Observable object 13 | * with the moving average of the quote lengths. 14 | * Each time a new quote arrives, a new average value should be emitted. 15 | */ 16 | object Ex3 extends App { 17 | 18 | 19 | @tailrec 20 | def randomString(length: Int, l: List[Char] = List.empty[Char]):List[Char] = { 21 | if (length == 1) util.Random.nextPrintableChar :: l 22 | else randomString(length-1,util.Random.nextPrintableChar :: l) 23 | } 24 | 25 | def randomQuoteMock = Observable.interval(1 seconds).map((l) => randomString(Random.nextInt(10)+1)) 26 | 27 | randomQuoteMock.scan((0D,0)) { 28 | (n, q) => n match { 29 | case (s, c) => (s + q.length, c + 1) 30 | } 31 | } 32 | .tail 33 | .map((e) => e._1 / e._2) 34 | .subscribe((e) => log(s"avg = $e")) 35 | 36 | Thread.sleep(10000) 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/exercises/ch6/Ex4.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package exercises 3 | package ch6 4 | 5 | /** 6 | Implement the reactive signal abstraction, represented with the Signal[T] type. 7 | 8 | The Signal[T] type comes with the method apply, used to query the last event emitted by this signal, 9 | and several combinators with the same semantics as the corresponding Observable methods: 10 | 11 | class Signal[T] { 12 | def apply(): T = ??? 13 | def map(f: T => S): Signal[S] = ??? 14 | def zip[S](that: Signal[S]): Signal[(T, S)] = ??? 15 | def scan[S](z: S)(f: (S, T) => S) = ??? 16 | } 17 | 18 | Then, add the method toSignal to the Observable[T] type, which converts 19 | an Observable object to a reactive signal: def toSignal: Signal[T] = ??? 20 | 21 | Consider using Rx subjects for this task. 22 | */ 23 | 24 | import rx.lang.scala._ 25 | 26 | object Ex4 extends App { 27 | 28 | implicit class ObserverableAdditional[T](val self:Observable[T]) extends AnyVal { 29 | 30 | def toSignal:Signal[T] = { 31 | new Signal[T](self) 32 | } 33 | 34 | } 35 | 36 | class Signal[T] { 37 | protected var lastEvent: Option[T] = None 38 | protected var observable: Observable[T] = _ 39 | 40 | def this(observable: Observable[T]) { 41 | this() 42 | setObservable(observable) 43 | } 44 | 45 | def this(observable: Observable[T], initial: T) { 46 | this(observable) 47 | lastEvent = Option(initial) 48 | } 49 | 50 | protected def setObservable(observable: Observable[T]): Unit = { 51 | this.observable = observable 52 | this.observable.subscribe(t => { lastEvent = Option(t) }) 53 | } 54 | 55 | /* This method throws `NoSuchElementException` when any of events are not emitted. */ 56 | def apply(): T = lastEvent.get 57 | 58 | def map[S](f: T => S): Signal[S] = lastEvent match { 59 | case Some(t) => new Signal(observable.map(f), f(t)) 60 | case _ => new Signal(observable.map(f)) 61 | } 62 | 63 | def zip[S](that: Signal[S]): Signal[(T, S)] = (lastEvent, that.lastEvent) match { 64 | case (Some(t), Some(s)) => new Signal(observable.zip(that.observable), (t, s)) 65 | case (_, _) => new Signal(observable.zip(that.observable)) 66 | } 67 | 68 | def scan[S](z: S)(f: (S, T) => S): Signal[S] = 69 | new Signal(observable.scan(z)(f)) 70 | } 71 | 72 | val sub1 = Subject[Int]() 73 | val sig1 = sub1.toSignal 74 | sub1.onNext(1) 75 | assert(sig1() == 1) 76 | sub1.onNext(2) 77 | assert(sig1() == 2) 78 | 79 | val sub2 = Subject[Int]() 80 | val sig2 = sub2.toSignal 81 | sub2.onNext(1) 82 | val increment = sig2.map(_ + 1) 83 | assert(increment() == 2) 84 | sub2.onNext(2) 85 | assert(increment() == 3) 86 | 87 | val sub31 = Subject[Int]() 88 | val sub32 = Subject[String]() 89 | val sig31 = sub31.toSignal 90 | val sig32 = sub32.toSignal 91 | sub31.onNext(1) 92 | sub32.onNext("a") 93 | val zipped = sig31.zip(sig32) 94 | assert(zipped() == (1, "a")) 95 | sub31.onNext(2) 96 | sub32.onNext("b") 97 | assert(zipped() == (2, "b")) 98 | 99 | val sub4 = Subject[Int]() 100 | val sig4 = sub4.toSignal 101 | sub4.onNext(1) 102 | val sum = sig4.scan(10)(_ + _) 103 | assert(sum() == 10) 104 | sub4.onNext(2) 105 | assert(sum() == 12) 106 | sub4.onNext(3) 107 | assert(sum() == 15) 108 | 109 | } 110 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/exercises/ch6/Ex5.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package exercises 3 | package ch6 4 | 5 | /** 6 | * Implement the reactive cell abstraction, represented with the RCell[T] type: 7 | * class RCell[T] extends Signal[T] { 8 | * def :=(x: T): Unit = ??? 9 | * } 10 | * 11 | * A reactive cell is simultaneously a reactive signal from the previous exercise. 12 | * Calling the := method sets a new value to the reactive cell, and emits an event. 13 | */ 14 | 15 | import rx.lang.scala._ 16 | 17 | object Ex5 extends App { 18 | 19 | class RCell[T] extends Ex4.Signal[T] { 20 | private[this] val subject = Subject[T]() 21 | setObservable(subject) 22 | 23 | def :=(x: T): Unit = { 24 | subject.onNext(x) 25 | } 26 | } 27 | 28 | val rc1 = new RCell[Int]() 29 | rc1 := 1 30 | assert(rc1() == 1) 31 | 32 | val rc2 = new RCell[Int]() 33 | rc2 := 1 34 | val increment = rc2.map(_ + 1) 35 | assert(increment() == 2) 36 | rc2 := 2 37 | assert(increment() == 3) 38 | 39 | val rc31 = new RCell[Int]() 40 | val rc32 = new RCell[String]() 41 | rc31 := 1 42 | rc32 := "a" 43 | val zipped = rc31.zip(rc32) 44 | assert(zipped() == (1, "a")) 45 | rc31 := 2 46 | rc32 := "b" 47 | assert(zipped() == (2, "b")) 48 | 49 | val rc4 = new RCell[Int]() 50 | rc4 := 1 51 | val sum = rc4.scan(10)(_ + _) 52 | assert(sum() == 10) 53 | rc4 := 2 54 | assert(sum() == 12) 55 | rc4 := 3 56 | assert(sum() == 15) 57 | 58 | } -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/exercises/ch6/Ex6.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package exercises 3 | package ch6 4 | 5 | /** 6 | * Implement the reactive map collection, represented with the RMap class: 7 | * class RMap[K, V] { 8 | * def update(k: K, v: V): Unit 9 | * def apply(k: K): Observable[V] 10 | * } 11 | * 12 | * The update method behaves like the update on a regular Map collection. 13 | * Calling apply on a reactive map returns an Observable object with all the subsequent updates of the specific key. 14 | */ 15 | 16 | import rx.lang.scala._ 17 | 18 | object Ex6 extends App { 19 | 20 | class RMap[K, V] { 21 | import scala.collection._ 22 | private[this] val allSubscribers = mutable.Map[K, (Subject[V], mutable.Set[Subscriber[V]])]() 23 | private[this] val map = mutable.Map[K, V]() 24 | 25 | def update(k: K, v: V): Unit = { 26 | map(k) = v 27 | allSubscribers.get(k) match { 28 | case Some(s) => s._1.onNext(v) 29 | case _ => 30 | } 31 | } 32 | 33 | def apply(k: K): Observable[V] = Observable[V] { subscriber => 34 | val (subject, subscribers) = 35 | allSubscribers.getOrElseUpdate(k, (Subject[V](), mutable.Set.empty[Subscriber[V]])) 36 | subscribers += subscriber 37 | 38 | val subscription = subject.subscribe(subscriber) 39 | 40 | subscriber.add(Subscription { 41 | subscription.unsubscribe() 42 | 43 | subscribers -= subscriber 44 | if (subscribers.isEmpty) { 45 | allSubscribers -= k 46 | } 47 | }) 48 | } 49 | 50 | /* return true if there is at least one subscriber which subscribes to the updates of the specific key. */ 51 | def hasSubscribers(k: K): Boolean = allSubscribers.get(k).isDefined 52 | } 53 | 54 | import scala.collection.mutable.ListBuffer 55 | 56 | val rmap = new RMap[String, Int]() 57 | 58 | val key = "a" 59 | val o = rmap(key) 60 | assert(rmap.hasSubscribers(key) == false) 61 | 62 | val buf1 = ListBuffer.empty[Int] 63 | val subscription1 = o.subscribe(buf1 += _) 64 | val buf2 = ListBuffer.empty[Int] 65 | val subscription2 = o.subscribe(buf2 += _) 66 | 67 | rmap(key) = 1 68 | rmap(key) = 2 69 | assert(buf1 == ListBuffer(1, 2), buf1) 70 | assert(buf2 == ListBuffer(1, 2), buf2) 71 | 72 | subscription1.unsubscribe() 73 | assert(rmap.hasSubscribers(key)) 74 | subscription2.unsubscribe() 75 | assert(rmap.hasSubscribers(key) == false) 76 | 77 | } -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/exercises/ch6/Ex7.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package exercises 3 | package ch6 4 | 5 | /** 6 | * Implement the reactive priority queue, represented with the RPriorityQueue class: 7 | * class RPriorityQueue[T] { 8 | * def add(x: T): Unit = ??? 9 | * def pop(): T = ??? 10 | * def popped: Observable[T] = ??? 11 | * } 12 | * 13 | * The reactive priority queue exposes the Observable object popped, 14 | * which emits events whenever the smallest element in the priority queue gets removed by calling pop. 15 | */ 16 | 17 | import rx.lang.scala._ 18 | 19 | object Ex7 extends App { 20 | 21 | class RPriorityQueue[T](implicit val ord: Ordering[T]) { 22 | private[this] val pq = new scala.collection.mutable.PriorityQueue[T]()(ord.reverse) 23 | private[this] val subject = Subject[T]() 24 | 25 | def add(x: T): Unit = { 26 | pq += x 27 | } 28 | 29 | /* This method throws `NoSuchElementException` if the queue is empty. */ 30 | def pop(): T = { 31 | val x = pq.dequeue() 32 | subject.onNext(x) 33 | x 34 | } 35 | 36 | def popped: Observable[T] = subject 37 | } 38 | 39 | import scala.collection.mutable.ListBuffer 40 | 41 | val rqueue = new RPriorityQueue[Int]() 42 | rqueue.add(3) 43 | rqueue.add(1) 44 | rqueue.add(2) 45 | 46 | val o = rqueue.popped 47 | val buf = ListBuffer.empty[Int] 48 | o.subscribe(buf += _) 49 | 50 | assert(rqueue.pop() == 1) 51 | assert(rqueue.pop() == 2) 52 | assert(rqueue.pop() == 3) 53 | assert(buf == ListBuffer(1, 2, 3)) 54 | 55 | } -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/exercises/ch7/ex1.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package exercises 3 | package ch7 4 | 5 | /** 6 | * Implement the transactional pair abstraction, represented with the TPair class: 7 | * 8 | * class TPair[P, Q](pinit: P, qinit: Q) { 9 | * def first(implicit txn: InTxn): P = ??? 10 | * def first_=(x: P)(implicit txn: InTxn): P = ??? 11 | * def second(implicit txn: InTxn): Q = ??? 12 | * def second_=(x: Q)(implicit txn: InTxn): Q = ??? 13 | * def swap()(implicit e: P =:= Q, txn: InTxn): Unit = ??? 14 | * } 15 | * 16 | * In addition to getters and setters for the two fields, 17 | * the transactional pair defines the swap method that swaps the fields, 18 | * and can only be called if the types P and Q are the same. 19 | */ 20 | object Ex1 extends App { 21 | 22 | import scala.concurrent._ 23 | import ExecutionContext.Implicits.global 24 | import scala.concurrent.stm._ 25 | 26 | class TPair[P, Q](pinit: P, qinit: Q) { 27 | 28 | private val rFirst = Ref[P](pinit) 29 | private val rSecond = Ref[Q](qinit) 30 | 31 | 32 | def first(implicit txn: InTxn): P = rFirst.single() 33 | 34 | def first_=(x: P)(implicit txn: InTxn) = rFirst.single.transform(old => x) 35 | 36 | def second(implicit txn: InTxn): Q = rSecond.single() 37 | 38 | def second_=(x: Q)(implicit txn: InTxn) = rSecond.single.transform(old => x) 39 | 40 | def swap()(implicit e: P =:= Q, txn: InTxn): Unit = { 41 | val old = first 42 | first = second.asInstanceOf[P] 43 | second = e(old) 44 | } 45 | } 46 | 47 | //test 48 | val p = new TPair[String,String]("first value","second value") 49 | 50 | def swapOne = atomic { implicit txn => 51 | p.swap 52 | 53 | val vF = p.first 54 | val vS = p.second 55 | 56 | Txn.afterCommit { _ => 57 | assert(vS != vF) 58 | } 59 | } 60 | 61 | (1 to 1001).map(_ => Future { 62 | swapOne 63 | }) 64 | 65 | Thread.sleep(2000) 66 | 67 | atomic {implicit txn => 68 | log(s"Result: first = '${p.first}' vSecond = '${p.second}'") 69 | } 70 | 71 | 72 | } -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/exercises/ch7/ex2.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package exercises 3 | package ch7 4 | 5 | /** 6 | * Use ScalaSTM to implement the mutable location abstraction from Haskell, 7 | * represented with the MVar class: 8 | * 9 | * class MVar[T] { 10 | * def put(x: T)(implicit txn: InTxn): Unit = ??? 11 | * def take()(implicit txn: InTxn): T = ??? 12 | * } 13 | * 14 | * An MVar object can be either full or empty. 15 | * Calling put on a full MVar object blocks until the MVar object becomes empty, 16 | * and adds an element. 17 | * 18 | * Similarly, calling take on an empty MVar object blocks until the MVar object becomes full, 19 | * and removes the element. 20 | * 21 | * Now, implement a method called swap, which takes two MVar objects and swaps their values: 22 | * 23 | * def swap[T](a: MVar[T], b: MVar[T])(implicit txn: InTxn) = ??? 24 | * 25 | * Contrast the MVar class with the SyncVar class from Chapter 2, 26 | * Concurrency on the JVM and the Java Memory Model. 27 | * Is it possible to implement the swap method for SyncVar objects 28 | * without modifying the internal implementation of the SyncVar class? 29 | * 30 | */ 31 | object Ex2 extends App { 32 | 33 | import java.util.concurrent.atomic.AtomicInteger 34 | import scala.concurrent._ 35 | import ExecutionContext.Implicits.global 36 | import scala.concurrent.stm._ 37 | 38 | class MVar[T] { 39 | 40 | private val rx = Ref[Option[T]](None) 41 | 42 | def put(x: T)(implicit txn: InTxn): Unit = rx() match { 43 | case Some(_) => retry 44 | case None => rx() = Some(x) 45 | } 46 | 47 | def take()(implicit txn: InTxn): T = rx() match { 48 | case Some(x) => { 49 | rx() = None 50 | x 51 | } 52 | case None => retry 53 | } 54 | } 55 | 56 | def swap[T](a: MVar[T], b: MVar[T])(implicit txn: InTxn) = { 57 | val old = a.take 58 | a.put(b.take()) 59 | b.put(old) 60 | } 61 | 62 | //test 63 | val mVar = new MVar[Integer] 64 | 65 | val l = 1 to 1001 66 | 67 | l.map( 68 | i => Future { 69 | atomic {implicit txn => 70 | mVar.put(i) 71 | } 72 | } 73 | ) 74 | 75 | val sum = new AtomicInteger(0) 76 | 77 | l.map( 78 | i => Future { 79 | atomic {implicit txn => 80 | val i = mVar.take 81 | Txn.afterCommit(_ => sum.addAndGet(i)) 82 | } 83 | } 84 | ) 85 | 86 | Thread.sleep(5000) 87 | 88 | if (l.sum != sum.get) log(s"Error !!!! ${l.sum} != $sum") 89 | 90 | log(s"summ = ${sum.get}") 91 | 92 | //test swap 93 | log("--- test swap ------------") 94 | 95 | val mva = new MVar[String] 96 | val mvb = new MVar[String] 97 | atomic {implicit txn => 98 | mva.put("a") 99 | mvb.put("b") 100 | } 101 | 102 | l.map(i => 103 | Future{ 104 | atomic {implicit txn => 105 | swap(mva, mvb) 106 | } 107 | } 108 | ) 109 | 110 | Thread.sleep(5000) 111 | 112 | atomic {implicit txn => 113 | val a = mva.take 114 | val b = mvb.take 115 | 116 | Txn.afterCommit( _ => log(s"a= $a, b = $b")) 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/exercises/ch7/ex3.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package exercises 3 | package ch7 4 | 5 | /** 6 | * Implement the atomicRollbackCount method, which is used to track how many times 7 | * a transaction was rolled back before it completed successfully: 8 | * 9 | * def atomicRollbackCount[T](block: InTxn => T): (T, Int) = ??? 10 | */ 11 | object Ex3 extends App { 12 | 13 | import scala.concurrent.ExecutionContext 14 | import scala.concurrent.Future 15 | import scala.concurrent.stm._ 16 | import ExecutionContext.Implicits.global 17 | import scala.util.Random 18 | 19 | def atomicRollbackCount[T](block: InTxn => T): (T, Int) = { 20 | var cnt = 0 21 | atomic { implicit txn => 22 | Txn.afterRollback(_ => 23 | cnt += 1 24 | ) 25 | (block(txn), cnt) 26 | } 27 | } 28 | 29 | //test 30 | val r = Ref(10) 31 | 32 | def block(txn: InTxn): Int = { 33 | var x: Int = r.get(txn) 34 | x = Random.nextInt(10000) 35 | Thread.sleep(10) 36 | r.set(x)(txn) 37 | x 38 | } 39 | 40 | (1 to 100).map(i => 41 | Future { 42 | atomicRollbackCount[Int](block) match { 43 | case (_, cnt) => log(s"Transaction: $i, retries = $cnt") 44 | case _ => log("???") 45 | } 46 | } 47 | ) 48 | 49 | Thread.sleep(3000) 50 | } 51 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/exercises/ch7/ex4.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package exercises 3 | package ch7 4 | 5 | /** 6 | * Implement the atomicWithRetryMax method, 7 | * which is used to start a transaction that can be retried at most n times: 8 | * 9 | * def atomicWithRetryMax[T](n: Int)(block: InTxn => T): T = ??? 10 | * 11 | * Reaching the maximum number of retries throws an exception. 12 | */ 13 | object Ex4 extends App { 14 | 15 | import scala.concurrent._ 16 | import ExecutionContext.Implicits.global 17 | import scala.concurrent.stm._ 18 | import scala.util.Random 19 | 20 | case class ReachMaxNumberException(cntRetries: Int) extends Exception 21 | 22 | def atomicWithRetryMax[T](n: Int)(block: InTxn => T): T = { 23 | 24 | var cntRetries = 0 25 | 26 | atomic{ implicit txn => 27 | Txn.afterRollback(_ => cntRetries += 1) 28 | 29 | if (cntRetries > n) { 30 | throw ReachMaxNumberException(cntRetries) 31 | } 32 | 33 | block(txn) 34 | } 35 | } 36 | 37 | //test 38 | val r = Ref(10) 39 | 40 | def block(txn: InTxn): Int = { 41 | var x: Int = r.get(txn) 42 | Thread.sleep(10) 43 | x += Random.nextInt(100) 44 | r.set(x)(txn) 45 | x 46 | } 47 | 48 | (1 to 100).map(i => 49 | Future { 50 | try { 51 | atomicWithRetryMax[Int](3)(block) 52 | log(s"Transaction: $i - ok") 53 | } catch { 54 | case ReachMaxNumberException(cntRetries) => log(s"Transaction: $i (retries = $cntRetries)") 55 | } 56 | } 57 | ) 58 | 59 | Thread.sleep(3000) 60 | } 61 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/exercises/ch7/ex5.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package exercises 3 | package ch7 4 | 5 | /** 6 | * Implement a transactional First In First Out (FIFO) queue, 7 | * represented with the TQueue class: 8 | * 9 | * class TQueue[T] { 10 | * def enqueue(x: T)(implicit txn: InTxn): Unit = ??? 11 | * def dequeue()(implicit txn: InTxn): T = ??? 12 | * } 13 | * 14 | * The TQueue class has similar semantics as scala.collection.mutable. Queue, 15 | * but calling dequeue on an empty queue blocks until a value becomes available. 16 | */ 17 | object Ex5 extends App { 18 | 19 | import scala.collection.immutable.Queue 20 | import scala.concurrent.stm._ 21 | import scala.concurrent.{ExecutionContext, Future} 22 | import ExecutionContext.Implicits.global 23 | 24 | class TQueue[T] { 25 | 26 | private val r = Ref[Queue[T]](Queue.empty[T]) 27 | 28 | def enqueue(x: T)(implicit txn: InTxn): Unit = { 29 | r() = r() :+ x 30 | } 31 | 32 | def dequeue()(implicit txn: InTxn): T = { 33 | r().dequeueOption match { 34 | case None => retry 35 | case Some((x,q)) => { 36 | r() = q 37 | x 38 | } 39 | } 40 | } 41 | } 42 | 43 | //test 44 | val tQueue = new TQueue[Integer] 45 | 46 | val l = 1 to 20 47 | 48 | l.map { i => 49 | Future { 50 | atomic {implicit txn => 51 | val x = tQueue.dequeue 52 | 53 | Txn.afterCommit{_ => 54 | log(s"dequeu: $x") 55 | } 56 | } 57 | } 58 | } 59 | 60 | l.map { i => 61 | Future { 62 | atomic {implicit txn => 63 | tQueue.enqueue(i) 64 | 65 | Txn.afterCommit { _ => 66 | log(s"enque: $i") 67 | } 68 | } 69 | } 70 | } 71 | 72 | Thread.sleep(1000) 73 | } 74 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/exercises/ch8/ex1.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package exercises 3 | package ch8 4 | 5 | import java.util.TimerTask 6 | 7 | import akka.actor._ 8 | import akka.pattern.pipe 9 | 10 | import scala.concurrent.ExecutionContext.Implicits.global 11 | import scala.concurrent.{Future, Promise} 12 | 13 | /** Implement the timer actor with the TimerActor class. 14 | * 15 | * After receiving a Register message containing the t timeout in milliseconds, 16 | * the timer actor sends a Timeout message back after t milliseconds. 17 | * 18 | * The timer must accept multiple Register messages. 19 | */ 20 | object Ex1 extends App { 21 | 22 | def timerFuture(t: Int): Future[Unit] = { 23 | val p = Promise[Unit] 24 | 25 | import java.util.Timer 26 | (new Timer(true)). 27 | schedule(new TimerTask { 28 | override def run(): Unit = p.success() 29 | }, t) 30 | 31 | p.future 32 | } 33 | 34 | import org.learningconcurrency.exercises.ch8.Ex1.TestActor.Register 35 | import org.learningconcurrency.exercises.ch8.Ex1.TimerActor.Timeout 36 | 37 | class TimerActor extends Actor { 38 | override def receive = { 39 | case Register(t) => 40 | log(s"REGISTER MESSAGE (timeout = $t) from $sender") 41 | timerFuture(t) map { (_) => Timeout } pipeTo sender 42 | } 43 | } 44 | 45 | object TimerActor { 46 | val props = Props[TimerActor] 47 | case object Timeout 48 | } 49 | 50 | //test 51 | 52 | class TestActor(t: Int) extends Actor { 53 | context.actorSelection("/user/timerActor") ! Register(t) 54 | 55 | override def receive: Receive = { 56 | case Timeout => log(s"TIMEOUT MESSAGE from ${sender}") 57 | } 58 | } 59 | 60 | object TestActor { 61 | case class Register(t: Int) 62 | def props(t: Int) = Props(classOf[TestActor], t) 63 | } 64 | 65 | val system = ActorSystem("MyActorSystem") 66 | val timerActor = system.actorOf(TimerActor.props, "timerActor") 67 | 68 | (1 to 10) map ((i) => system.actorOf(TestActor.props(i * 1000), s"testActor-$i")) 69 | 70 | Thread.sleep(12000) 71 | system.shutdown() 72 | 73 | 74 | } 75 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/exercises/ch8/ex2.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package exercises 3 | package ch8 4 | 5 | import akka.actor._ 6 | import akka.event.Logging 7 | /** 8 | * Recall the bank account example from Chapter 2, Concurrency on the JVM and the Java Memory Model. 9 | * Implement different bank accounts as separate actors, 10 | * represented with the AccountActor class. 11 | * When an AccountActor class receives a Send message, 12 | * it must transfer the specified amount of money to the target actor. 13 | * 14 | * What will happen if either of the actors receives a Kill message 15 | * at any point during the money transaction? 16 | */ 17 | object Ex2 extends App { 18 | 19 | /** 20 | * If the actor receives a Kill message at any point during the transaction, 21 | * The actor will restart and it will lost his state (amount) 22 | * This problem can't be solved easily in the general case. 23 | */ 24 | class AccountActor(name: String, var money: Int) extends Actor { 25 | 26 | val log = Logging(context.system, this) 27 | 28 | override def receive: Receive = { 29 | case AccountActor.PlusMoney(amount) => 30 | money += amount 31 | sender ! AccountActor.Ok 32 | case AccountActor.MinusMoney(amount) if money >= amount => 33 | money -= amount 34 | sender ! AccountActor.Ok 35 | case AccountActor.MinusMoney(amount) => 36 | log.error(s"Insufficient funds. ($money < $amount)") 37 | sender ! AccountActor.Error 38 | case AccountActor.Print => log.info(s"$name: $money") 39 | } 40 | } 41 | 42 | object AccountActor { 43 | case class PlusMoney(amount: Int) 44 | case class MinusMoney(amount: Int) 45 | 46 | case object Ok 47 | case object Error 48 | 49 | case object Print 50 | 51 | def props(name: String, money: Int) = Props(classOf[AccountActor], name, money) 52 | } 53 | 54 | class TransactionActor extends Actor { 55 | 56 | val log = Logging(context.system, this) 57 | 58 | def checkTransferTo: Receive = { 59 | case AccountActor.Ok => log.info("Transfer complete") 60 | } 61 | 62 | def checkTransferFrom(accountTo: ActorRef, amount: Int): Receive = { 63 | case AccountActor.Ok => 64 | accountTo ! AccountActor.PlusMoney(amount) 65 | context.become(checkTransferTo) 66 | case AccountActor.Error => 67 | log.error("Transfer error (from)") 68 | context.stop(self) 69 | } 70 | 71 | override def receive: Actor.Receive = { 72 | case TransactionActor.StartTransaction(accountFrom, accountTo, amount) => 73 | accountFrom ! AccountActor.MinusMoney(amount) 74 | context.become(checkTransferFrom(accountTo, amount)) 75 | } 76 | } 77 | 78 | object TransactionActor { 79 | case class StartTransaction(accountFrom: ActorRef, accountTo: ActorRef, amount: Int) 80 | } 81 | 82 | //test 83 | val system = ActorSystem("AccountSystem") 84 | 85 | val first = system.actorOf(AccountActor.props("Account A", 10), "account_A") 86 | val second = system.actorOf(AccountActor.props("Account B", 0), "account_B") 87 | 88 | val transaction = system.actorOf(Props[TransactionActor], "transfer") 89 | transaction ! TransactionActor.StartTransaction(accountFrom = first, accountTo = second, amount = 7) 90 | 91 | Thread.sleep(1000) 92 | 93 | first ! AccountActor.Print 94 | second ! AccountActor.Print 95 | 96 | Thread.sleep(2000) 97 | 98 | system.shutdown() 99 | 100 | } 101 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/exercises/ch8/ex3.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package exercises 3 | package ch8 4 | 5 | import akka.actor.{Actor, ActorRef, ActorSystem, Props} 6 | import akka.event.Logging 7 | import org.learningconcurrency.exercises.ch8.Ex3.SessionActor.{EndSession, StartSession} 8 | 9 | /** 10 | * Implement the SessionActor class, for actors that control access to other actors: 11 | * class SessionActor(password: String, r: ActorRef) extends Actor { 12 | * def receive = ??? 13 | * } 14 | * After the SessionActor instance receives the StartSession message with the correct password, 15 | * it forwards all the messages to the actor reference r, 16 | * until it receives the EndSession message. 17 | * Use behaviors to model this actor. 18 | */ 19 | object Ex3 extends App { 20 | 21 | class SessionActor(password: String, r: ActorRef) extends Actor { 22 | 23 | val log = Logging(context.system, this) 24 | 25 | override def receive: Receive = waitStart 26 | 27 | def waitStart:Receive = { 28 | case StartSession(p) if (p == password) => 29 | context.become(receiveMessage) 30 | log.info("start session") 31 | case m => log.info(s"Can't forward $m. Waiting start session ...") 32 | } 33 | 34 | def receiveMessage: Receive = { 35 | case EndSession => 36 | context.become(waitStart) 37 | log.info("end session") 38 | case m => r forward m 39 | } 40 | } 41 | 42 | object SessionActor { 43 | 44 | def props(password:String, r:ActorRef) = Props(classOf[SessionActor],password,r) 45 | 46 | case class StartSession(password: String) 47 | case object EndSession 48 | } 49 | 50 | 51 | //test 52 | class TestActor extends Actor { 53 | val log = Logging(context.system, this) 54 | 55 | override def receive: Actor.Receive = { 56 | case m => log.info(m.toString) 57 | } 58 | 59 | } 60 | 61 | object TestActor { 62 | val props = Props[TestActor] 63 | } 64 | 65 | val system = ActorSystem("Ch3System") 66 | 67 | val testActor = system.actorOf(TestActor.props,"TestActor") 68 | val sessionActor = system.actorOf(SessionActor.props("123",testActor)) 69 | 70 | sessionActor ! "Test1" 71 | sessionActor ! StartSession("123") 72 | sessionActor ! "Test2" 73 | sessionActor ! "Test3" 74 | sessionActor ! EndSession 75 | sessionActor ! "Test4" 76 | 77 | Thread.sleep(5000) 78 | 79 | system.shutdown() 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/exercises/ch8/ex4.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package exercises 3 | package ch8 4 | 5 | import akka.actor._ 6 | import akka.event.Logging 7 | import akka.pattern._ 8 | 9 | import scala.concurrent.ExecutionContext 10 | import scala.concurrent.ExecutionContext.Implicits.global 11 | import scala.concurrent.duration._ 12 | import scala.util.Try 13 | 14 | import scala.util.{Failure, Success} 15 | 16 | 17 | /** 18 | * Use actors to implement the ExecutionContext interface, 19 | * described in Chapter 3, Traditional Building Blocks of Concurrency. 20 | */ 21 | object Ex4 extends App { 22 | 23 | class ActorExecutionContext extends ExecutionContext { 24 | 25 | 26 | class ExecutorActor extends Actor() { 27 | 28 | val actorLog = Logging(context.system, this) 29 | 30 | override def receive: Receive = { 31 | case ExecutorActor.Execute(runnable) => 32 | Try(runnable.run()) match { 33 | case Success(_) => actorLog.info("result OK") 34 | case Failure(e) => reportFailure(e) 35 | } 36 | } 37 | } 38 | 39 | object ExecutorActor { 40 | case class Execute(runnable: Runnable) 41 | def props = Props(new ExecutorActor) 42 | } 43 | 44 | val system = ActorSystem("MyActorSystem") 45 | val executeActor = system.actorOf(ExecutorActor.props) 46 | 47 | override def execute(runnable: Runnable): Unit = executeActor ! ExecutorActor.Execute(runnable) 48 | 49 | override def reportFailure(cause: Throwable): Unit = log(s"error: ${cause.getMessage}") 50 | 51 | def shutdown() = system.shutdown() 52 | } 53 | 54 | 55 | val executionContext = new ActorExecutionContext() 56 | 57 | executionContext.execute(new Runnable { 58 | override def run(): Unit = { 59 | log("run (exception)") 60 | throw new Exception("test exception") 61 | } 62 | }) 63 | 64 | executionContext.execute(new Runnable { 65 | override def run(): Unit = { 66 | log("run") 67 | } 68 | }) 69 | 70 | Thread.sleep(2000) 71 | 72 | executionContext.shutdown() 73 | 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/exercises/ch8/ex5.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package exercises 3 | package ch8 4 | 5 | import akka.actor._ 6 | import akka.event.Logging 7 | import akka.pattern._ 8 | 9 | import scala.concurrent.duration._ 10 | import scala.util.{Failure, Success} 11 | 12 | import scala.concurrent.ExecutionContext.Implicits.global 13 | 14 | /** 15 | * Implement the FailureDetector actor, which sends Identify messages to the specified 16 | * actors every interval seconds. 17 | * If an actor does not reply with any ActorIdentity messages within threshold seconds, 18 | * the FailureDetector actor sends a Failed message to its parent actor, 19 | * which contains the actor reference of the failed actor. 20 | */ 21 | object Ex5 extends App { 22 | 23 | class FailureDetectorActor(r: ActorRef, interval: Int, threshold: Int) extends Actor { 24 | 25 | val log = Logging(context.system, this) 26 | 27 | val cancelable = context.system.scheduler.schedule( 28 | initialDelay = 0 seconds, interval = interval seconds, receiver = self, message = FailureDetectorActor.Iteration) 29 | 30 | override def receive: Receive = { 31 | case FailureDetectorActor.Iteration => 32 | log.info("send identify message") 33 | (r ? Identify(r.path))(threshold seconds) onComplete { 34 | case Success(_) => log.info("OK") 35 | case Failure(e) => 36 | log.info(s"$r Not reply ") 37 | context.actorSelection(r.path.parent) ! ParentActor.Failed(r) 38 | } 39 | case FailureDetectorActor.StopTask => 40 | log.info(s"Stop task") 41 | cancelable.cancel() 42 | } 43 | } 44 | 45 | object FailureDetectorActor { 46 | case object Iteration 47 | case object StopTask 48 | def props(r: ActorRef, interval: Int, threshold: Int) = Props(classOf[FailureDetectorActor], r, interval, threshold) 49 | } 50 | 51 | //test 52 | class TestActor extends Actor { 53 | override def receive: Actor.Receive = PartialFunction.empty 54 | } 55 | 56 | object TestActor { 57 | val props = Props[TestActor] 58 | } 59 | 60 | class ParentActor extends Actor { 61 | 62 | val log = Logging(context.system, this) 63 | 64 | val testActor = context.actorOf(TestActor.props, "TestActor") 65 | val failureDetectorActor = system.actorOf(FailureDetectorActor.props(testActor, 1, 2), "FailureDetector") 66 | 67 | override def receive: Actor.Receive = { 68 | case ParentActor.StopTestActor => 69 | log.info("stop test actor") 70 | context.stop(testActor) 71 | case ParentActor.Failed(r) => 72 | log.info(s"Parent. $r not reply !!!!") 73 | failureDetectorActor ! FailureDetectorActor.StopTask 74 | } 75 | } 76 | 77 | object ParentActor { 78 | 79 | case class Failed(r: ActorRef) 80 | 81 | case object StopTestActor 82 | 83 | val props = Props[ParentActor] 84 | 85 | } 86 | 87 | import org.learningconcurrency.exercises.ch8.Ex5.ParentActor._ 88 | 89 | val system = ActorSystem("FailureDetectorSystem") 90 | val parentActor = system.actorOf(ParentActor.props, "ParentActor") 91 | 92 | Thread.sleep(5000) 93 | parentActor ! StopTestActor 94 | Thread.sleep(5000) 95 | 96 | system.shutdown() 97 | 98 | } 99 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/exercises/ch8/ex6.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | package exercises 3 | package ch8 4 | 5 | import akka.actor.{Props, ActorSystem, Actor, ActorRef} 6 | import akka.pattern._ 7 | import akka.util.Timeout 8 | 9 | import scala.collection.immutable.IndexedSeq 10 | import scala.concurrent.duration._ 11 | 12 | import scala.concurrent.ExecutionContext.Implicits.global 13 | 14 | import scala.concurrent.Future 15 | import scala.util.Success 16 | import scala.util.Failure 17 | 18 | /** 19 | * A distributed hash map is a collection distributed across multiple computers, 20 | * each of which contains part of the data, called a shard. 21 | * When there are 2^n shards, the first n bits of the hash code of the key are used 22 | * to decide which shard a key-value pair should go to. 23 | * 24 | * Implement the distributed hash map with the DistributedMap class: 25 | * class DistributedMap[K, V](shards: ActorRef*) { 26 | * def update(key: K, value: V): Future[Unit] = ??? 27 | * def get(key: K): Future[Option[V]] = ??? 28 | * } 29 | * 30 | * The DistributedMap class takes a list of actor references to the ShardActor instances, 31 | * whose actor template you also need to implement. 32 | * 33 | * You might assume that the length of the shards list is a power of two. 34 | * The update and get methods are asynchronous, and return the result in a future object. 35 | */ 36 | object Ex6 extends App { 37 | 38 | case class DistributedKey(shard: Int, key: Int) 39 | 40 | import org.learningconcurrency.exercises.ch8.Ex6.MapActor._ 41 | 42 | class DistributedMap[K, V](shards: ActorRef*) { 43 | 44 | implicit val timeout: Timeout = 5 seconds 45 | 46 | val shardsArray = shards.toArray 47 | val n = (Math.log10(shardsArray.length) / Math.log10(2)).toInt 48 | 49 | def getDistributedKey(key: Int, n: Int): DistributedKey = { 50 | 51 | def getKey(bs: String, n: Int): DistributedKey = { 52 | DistributedKey(Integer.parseInt(bs.takeRight(n), 2), 53 | if (bs.length > n) Integer.parseInt(bs.take(bs.length - n), 2) else 0) 54 | } 55 | 56 | getKey(key.toBinaryString, n) 57 | } 58 | 59 | def update(key: K, value: V): Future[Unit] = { 60 | val dk = getDistributedKey(key = key.hashCode, n = n) 61 | (shardsArray(dk.shard) ? Update(dk.key, value)).mapTo[Unit] 62 | } 63 | 64 | def get(key: K): Future[Option[V]] = { 65 | val dk = getDistributedKey(key = key.hashCode, n = n) 66 | (shardsArray(dk.shard) ? Get(dk.key)).mapTo[Option[V]] 67 | } 68 | } 69 | 70 | class MapActor[V] extends Actor { 71 | 72 | var m = Map.empty[Int, V] 73 | 74 | override def receive: Receive = { 75 | case Get(key: Int) => 76 | sender() ! m.get(key) 77 | case Update(key, value: V) => 78 | log(s"update $key -> $value, actor = ${this.self.path} ") 79 | m = m + (key -> value) 80 | } 81 | } 82 | 83 | object MapActor { 84 | 85 | case class Get(key: Int) 86 | 87 | case class Update[V](key: Int, value: V) 88 | 89 | val props = Props(classOf[MapActor[String]]) 90 | } 91 | 92 | //test 93 | 94 | val system = ActorSystem("distributedSystem") 95 | val actors = (0 until 4).map((i) => system.actorOf(MapActor.props, s"mapActor-$i")) 96 | 97 | val distributedMap = new DistributedMap[Int, String](actors: _*) 98 | 99 | val values = List((0, "A"), (1, "B"), (2, "C"), (3, "D"), (4, "E"), (5,"F"), (6,"G")) 100 | 101 | values foreach { case (k, v) => distributedMap.update(k, v) } 102 | 103 | val fl = values map ((x) => distributedMap.get(x._1)) 104 | fl foreach ((f) => f foreach { case v => log(s"v = $v") }) 105 | 106 | Thread.sleep(5000) 107 | system.shutdown() 108 | } 109 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/exercises/package.scala: -------------------------------------------------------------------------------- 1 | package org.learningconcurrency 2 | 3 | 4 | package object exercises { 5 | } 6 | -------------------------------------------------------------------------------- /src/main/scala/org/learningconcurrency/package.scala: -------------------------------------------------------------------------------- 1 | package org 2 | 3 | 4 | 5 | 6 | 7 | 8 | package object learningconcurrency { 9 | 10 | def log(msg: String) { 11 | println(s"${Thread.currentThread.getName}: $msg") 12 | } 13 | 14 | } 15 | 16 | --------------------------------------------------------------------------------