如何使Scala对象成为线程安全的。
class Stack {
case class Node(value: Int, var next: Node)
private var head: Node = null
private var sz = 0
def push(newValue: Int) {
head = Node(newValue, head)
sz += 1
}
def pop() = {
val oldNode = head
head = oldNode.next
oldNode.next = null
sz -= 1
}
def size = sz //I am accessing sz from two threads
}
这个类显然不是线程安全的。我想让它成为线程安全的。
先谢谢,
HP
答案 0 :(得分:6)
只是因为它很有趣,你也可以通过将head
弹出到AtomicReference并完全避免synchronized
来使这个线程安全。正是如此:
final class Stack {
private val head = new AtomicReference[Node](Nil)
@tailrec
def push(newValue: Int) {
val current = head.get()
if (!head.compareAndSet(current, Node(newValue, current))) {
push(newValue)
}
}
@tailrec
def pop(): Option[Int] = head.get() match {
case current @ Cons(v, tail) => {
if (!head.compareAndSet(current, tail))
pop()
else
Some(v)
}
case Nil => None
}
def size = {
def loop(node: Node, size: Int): Int = node match {
case Cons(_, tail) => loop(tail, size + 1)
case Nil => size
}
loop(head.get(), 0)
}
private sealed trait Node
private case class Cons(head: Int, tail: Node) extends Node
private case object Nil extends Node
}
这样可以完全避免锁定,并提供比synchronized
版本更好的吞吐量。值得注意的是,这种虚假的线程安全数据结构很少是一个好主意。在数据结构级别处理同步和状态管理问题有点像尝试在XML解析器中处理IO异常:您试图在错误的位置解决正确的问题而您没有所需的信息去做。例如,上面的堆栈是完全安全的,但它在操作中肯定不一致(例如,您可以推送并随后弹出到堆栈并因此得到None
。
如果您需要共享的可变状态,那么更好的选择是使用不可变堆栈(如List
)并将 放入AtomicReference
。
答案 1 :(得分:3)
在我看来,使这个有意义的线程安全的最简单方法如下:
class Stack {
case class Node(value: Int, var next: Node)
private var head: Node = null
private var sz : Int = 0
def push(newValue: Int) {
synchronized {
head = Node(newValue, head)
sz += 1
}
}
def pop() : Option[Int] = {
synchronized {
if ( sz >= 1 ) {
val ret = Some(head.value)
val oldNode = head
head = oldNode.next
oldNode.next = null
sz -= 1
ret
} else {
None
}
}
}
def size = synchronized { sz }
}
此实现将允许您确保push
和pop
是原子的,pop
返回Some
包装从中删除的值如果堆栈已经为空,则堆栈顶部或None
。
作为一个注释,对大小的访问是同步的,但是你无法保证它在返回后的任何时候都是正确的,因为多个线程能够访问堆栈,可能会改变它的大小。如果你确实需要准确地知道大小,那么你必须以不同的方式解决这个问题,在你使用它时在整个堆栈上进行同步。