Just because it's fun, you can also make this thread-safe by popping head
into an AtomicReference and avoiding synchronized
altogether. Thusly:
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
}
This avoids locking entirely and provides substantially better throughput than the synchronized
version. It's worth noting though that this sort of fake thread-safe data structure is rarely a good idea. Handling synchronization and state management concerns at the level of a data structure is a bit like trying to handle IO exceptions within an XML parser: you're trying to solve the right problem in the wrong place and you don't have the information needed to do that. For example, the stack above is perfectly safe, but it's certainly not consistent across operations (e.g. you could push and subsequently pop onto a stack and get None
as a result).
Your better option is to use an immutable stack (like List
) and throw that into an AtomicReference
if you need shared mutable state.
sz
: remember, it is operations and how they are used that must be atomic.)