Tuesday, October 2, 2012

Scala: Lazy values behind the scenes

Scala introduces some syntactic sugar to shorten the code we produce. One of them is lazy values. It's wonderful how fast a lazy initialization can be defined and used just by adding a single word 'lazy' for any value almost anywhere...

class LazyValue {
  lazy val x = 10
  lazy val y = "20"
}

Easy, isn't it? The values 'x' and 'y' are initialized with a first time access and a syntax to define this is so compact. But behind the scenes the lazy values are a bit more complicated. This is how scala compiler unwrap lazy values:

import scala.ScalaObject;

public class LazyValue implements ScalaObject {
    private int x;
    private String y;
    public volatile int bitmap$0;

    public int x() {
        if ((this.bitmap$0 & 0x1) == 0)
            synchronized (this) {
                if ((this.bitmap$0 & 0x1) == 0) {
                    this.x = 10;
                    this.bitmap$0 |= 1;
                }
            }
        return this.x;
    }

    public String y() {
        if ((this.bitmap$0 & 0x2) == 0)
            synchronized (this) {
                if ((this.bitmap$0 & 0x2) == 0) {
                    this.y = "20";
                    this.bitmap$0 |= 2;
                }

            }
        return this.y;
    }
}

Notice that instead of checking x==null and y==null scala uses bitwise operations on the volatile bitmap$0, a good practice by the way.  If we have more than 32 lazy fields then the bitmap$1 is introduced

The code has an issue we have to be aware of: the initialization is synchronizing on this. So when we'r working with lazy values we have to keep in mind the following:

  1. Accessing to a lazy val might lock our object (all it's synchronized methods and lazy vals) and vise-versa. 
  2. When we use lazy vals on objects we have to be super-careful cause deadlock is the thing we can face when using the cross-object initialization of lazy vals
The example bellow demonstrates how to get a deadlock with a minimal effort :)

object Deadlock {
  object A {
    lazy val x: Int = {
      val cdl: CountDownLatch = new CountDownLatch(1)
      new Thread(){
        override def run() {
          print(B.x)
          cdl.countDown()
        }
      }.start()
      cdl.await()
      10
    }
  }

  object B {

    lazy val x = A.x
  }

  def main(args: Array[String]) {
    print(B.x)
  }
}

Of course it's a synthetic example but it gives us an understanding that the parallel initialization of lazy values might be dangerous.

Summary: Use of lazy vals shorten our code by removing some boilerplates (in comparison to Java) but they should be used with an idea in mind that the initialization of lazy values is not an atomic operation and can lead to deadlock. 

No comments:

Post a Comment