我正在努力学习Scala,并发现它是一种很棒的语言 . 我从大卫波拉克那里学习"Beginning Scala" . 在第3章中有一段代码,它说明了如何在没有synchronized块的情况下编写多线程代码(这段代码是从书中复制的,可以从Apress site下载,我不打算在这里违反任何法律):
import java.util.concurrent.atomic.{AtomicReference => AtomR, AtomicLong}
import java.util.Random
import scala.collection.immutable.TreeHashMap
object Multics {
type MT = Map[String, Int]
val info: AtomR[MT] = new AtomR(TreeHashMap.empty)
val clashCnt = new AtomicLong
def main(argv: Array[String]) {
runThread {
repeatEvery(1000) {
println("Clash Count: "+clashCnt+" Total: "+
info.get.foldLeft(0)(_ + _._2))
} }
for (i old + (name -> 0)}
repeatEvery(ran.nextInt(100)) {
doSet(info){old => old + (name -> (old(name) + 1))}
cnt = cnt + 1
if (cnt != info.get()(name))
throw new Exception("Thread: "+name+" failed")
} }
}
def runThread(f: => Unit) =
(new Thread(new Runnable {def run(): Unit = f})).start
def doSet[T](atom: AtomR[T])(update: T => T) {
val old = atom.get
if (atom.compareAndSet(old, update(old))) ()
else {
clashCnt.incrementAndGet
doSet(atom)(update)
}
}
def repeatEvery(len: => Int)(body: => Unit): Unit = {
try {
while(true) {
Thread.sleep(len)
body
}
} catch {
case e => e.printStackTrace; System.exit(1)
}
}
}
根据我的理解,函数 doSet
中存在潜在的饥饿问题(不幸的线程可能总是冲突并导致StackOverflowException) . 我是对的,如果是的话,如何改进此代码来解决这个问题?
2 回答
首先,我认为你从书中的例子中粘贴的代码很缺乏;这让你很难理解你的问题 .
其次,doSet()可能会被递归多次调用,但在这种情况下不会发生StackOverflowException,因为有一个保存宽限:尾递归 . doSet()在函数末尾递归调用(在递归调用之后不再进行处理)并且不能被覆盖(在对象内定义),这使得它被优化为循环而没有任何堆栈开销 .
在2.8.0中,有一个名为@scala.annotation.tailrec的注释,当在def上使用时,它将确保def内的递归调用确实是尾递归的 .
看起来它使用Compare和Swap(http://en.wikipedia.org/wiki/Compare-and-swap)而不是锁定 .
这种方法的一般想法是循环,直到您设法一致地设置值 .
如果有饥饿问题,我不知道这个问题 . 我的猜测是理论上存在饥饿问题,但在实践中它会很好 .