Multithreading 如何使对象(可变堆栈)线程安全?
如何使Scala对象线程安全Multithreading 如何使对象(可变堆栈)线程安全?,multithreading,scala,thread-safety,Multithreading,Scala,Thread Safety,如何使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() = {
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在我看来,实现线程安全的最简单方法如下:
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
值得注意的是,对大小的访问是同步的,但是您无法保证它在返回后的任何时候都是正确的,因为多个线程能够访问堆栈,这可能会改变堆栈的大小。如果您确实需要准确地知道大小,那么您必须以不同的方式进行操作,在使用它时对整个堆栈进行同步。仅仅因为它很有趣,您还可以通过将
头
弹出到原子引用中并完全避免同步
来确保线程安全。因此:
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
。那么,为什么它不是线程安全的呢?您尝试了什么使其线程安全?(问题不仅仅在于sz
:记住,操作和它们的使用方式必须是原子的。)@Luigipling在返回大小时进行同步会稍微更精确,因为它不会在推送或弹出操作中返回大小,但是返回的值一收到就可能失效,因为另一个线程推送或弹出不会更新按大小返回的值。我将添加它,因为它总比没有好。另一种更有效的实现size
的方法是def size=sz
,并将@volatile
注释添加到sz
。在这种情况下,synchronized
只是对字段访问的过度使用。由于第4行中未定义节点,我得到了错误。若我在程序中定义了节点,那个么我就得到了已经定义的节点。谢谢你的解释。这很有帮助