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

时间:2011-11-29 22:10:24

标签: 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() = {
        val oldNode = head
        head = oldNode.next
        oldNode.next = null
        sz -= 1
    }

    def size = sz    //I am accessing sz from two threads
}

这个类显然不是线程安全的。我想让它成为线程安全的。

先谢谢,

HP

2 个答案:

答案 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 }
}

此实现将允许您确保pushpop是原子的,pop返回Some包装从中删除的值如果堆栈已经为空,则堆栈顶部或None

作为一个注释,对大小的访问是同步的,但是你无法保证它在返回后的任何时候都是正确的,因为多个线程能够访问堆栈,可能会改变它的大小。如果你确实需要准确地知道大小,那么你必须以不同的方式解决这个问题,在你使用它时在整个堆栈上进行同步。