2

How to make Scala object thread-safe.

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
}

This class is clearly not threadsafe. I want to make it threadsafe.

Thanks in Advance,

HP

2
  • 1
    So, why isn't it threadsafe? What have you tried to make it threadsafe? Commented Nov 29, 2011 at 22:22
  • (The issue isn't just with sz: remember, it is operations and how they are used that must be atomic.)
    – user166390
    Commented Nov 29, 2011 at 22:30

2 Answers 2

6

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.

1
  • I am getting error as Node not defined on line 4. If I define Node as in my program, I get Node already defined. Thanks for the explanation though. That was helpful Commented Dec 3, 2011 at 4:09
3

To my mind, the easiest way to make this meaningfully thread-safe would be as follows:

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

This implementation would allow you to ensure that push's and pop's would be atomic, with pop returning a Some wrapping the value it removed from the top of the stack or None if the stack was already empty.

As a note, access to the size is synchronized, but you have no way of guaranteeing that it will be correct at any point after it is returned, since multiple threads are able to access the stack, potentially altering its size. If you really do need to know the size exactly accurately, you would have to go about this differently, synchronizing on the whole stack when you use it.

2
  • @LuigiPlinge synchronizing when return size would be slightly more accurate, in that it would not return the size in the midst of a push or pop operation, but the value return could be invalidated as soon as it is received, since another thread pushing or popping would not update the value returned by size. I will add it in though, since it is better than nothing. Commented Nov 29, 2011 at 23:40
  • 2
    Another, more efficient way to implement size would be as def size = sz and to add the @volatile annotation to sz. In this case, synchronized is just overkill for field access. Commented Nov 30, 2011 at 2:51

Not the answer you're looking for? Browse other questions tagged or ask your own question.