Просто потому, что это весело, вы также можете сделать этот потокобезопасным, вставив 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: вы пытаетесь решить нужную проблему в неправильном месте и у вас нет информации, необходимой длясделай это.Например, вышеприведенный стек совершенно безопасен, но он определенно не согласован между операциями (например, вы можете поместить и затем вставить в стек и в результате получить None
).
Ваш лучший вариант - использоватьнеизменный стек (например, List
) и сбросьте , который , в AtomicReference
, если вам нужно общее изменяемое состояние.