Multithreading 如何使对象(可变堆栈)线程安全?

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() = {

如何使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

在我看来,实现线程安全的最简单方法如下:

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行中未定义节点,我得到了错误。若我在程序中定义了节点,那个么我就得到了已经定义的节点。谢谢你的解释。这很有帮助