This answer to What's the (hidden) cost of Scala's lazy val? shows how they were implemented in Scala 2.7. But as the comments say, this must have changed since then, so I'm curious, what's the current (2.10) implementation of class lazy val
variables?
Asked
Active
Viewed 2,188 times
17
2 Answers
24
Compiled this with scala 2.10.2:
class Foo {
lazy val bar = math.pow(5, 3)
}
Then decompiled the result with JD-GUI:
import scala.math.package.;
import scala.reflect.ScalaSignature;
@ScalaSignature(bytes="\006\001e1A!\001\002\001\013\t\031ai\\8\013\003\r\tq\001P3naRLhh\001\001\024\005\0011\001CA\004\013\033\005A!\"A\005\002\013M\034\027\r\\1\n\005-A!AB!osJ+g\rC\003\016\001\021\005a\"\001\004=S:LGO\020\013\002\037A\021\001\003A\007\002\005!A!\003\001EC\002\023\0051#A\002cCJ,\022\001\006\t\003\017UI!A\006\005\003\r\021{WO\0317f\021!A\002\001#A!B\023!\022\001\0022be\002\002")
public class Foo {
private double bar;
private volatile boolean bitmap$0;
private double bar$lzycompute() {
synchronized (this) {
if (!this.bitmap$0) {
this.bar = package..MODULE$.pow(5.0D, 3.0D);
this.bitmap$0 = true;
}
return this.bar;
}
}
public double bar() {
return this.bitmap$0 ? this.bar : bar$lzycompute();
}
}
Edit - Here's what it looks like for three fields:
class Foo {
lazy val a = math.pow(5, 1)
lazy val b = math.pow(5, 2)
lazy val c = math.pow(5, 3)
}
Decompiled:
import scala.math.package.;
import scala.reflect.ScalaSignature;
@ScalaSignature(bytes="\006\001\0052A!\001\002\001\013\t\031ai\\8\013\003\r\tq\001P3naRLhh\001\001\024\005\0011\001CA\004\013\033\005A!\"A\005\002\013M\034\027\r\\1\n\005-A!AB!osJ+g\rC\003\016\001\021\005a\"\001\004=S:LGO\020\013\002\037A\021\001\003A\007\002\005!A!\003\001EC\002\023\0051#A\001b+\005!\002CA\004\026\023\t1\002B\001\004E_V\024G.\032\005\t1\001A\t\021)Q\005)\005\021\021\r\t\005\t5\001A)\031!C\001'\005\t!\r\003\005\035\001!\005\t\025)\003\025\003\t\021\007\005\003\005\037\001!\025\r\021\"\001\024\003\005\031\007\002\003\021\001\021\003\005\013\025\002\013\002\005\r\004\003")
public class Foo {
private double a;
private double b;
private double c;
private volatile byte bitmap$0;
private double a$lzycompute() {
synchronized (this) {
if ((byte)(this.bitmap$0 & 0x1) == 0) {
this.a = package..MODULE$.pow(5.0D, 1.0D);
this.bitmap$0 = ((byte)(this.bitmap$0 | 0x1));
}
return this.a;
}
}
private double b$lzycompute() {
synchronized (this) {
if ((byte)(this.bitmap$0 & 0x2) == 0) {
this.b = package..MODULE$.pow(5.0D, 2.0D);
this.bitmap$0 = ((byte)(this.bitmap$0 | 0x2));
}
return this.b;
}
}
private double c$lzycompute() {
synchronized (this) {
if ((byte)(this.bitmap$0 & 0x4) == 0) {
this.c = package..MODULE$.pow(5.0D, 3.0D);
this.bitmap$0 = ((byte)(this.bitmap$0 | 0x4));
}
return this.c;
}
}
public double a() {
return (byte)(this.bitmap$0 & 0x1) == 0 ? a$lzycompute() : this.a;
}
public double b() {
return (byte)(this.bitmap$0 & 0x2) == 0 ? b$lzycompute() : this.b;
}
public double c() {
return (byte)(this.bitmap$0 & 0x4) == 0 ? c$lzycompute() : this.c;
}
}

Chris Martin
- 30,334
- 10
- 78
- 137
-
2Thank you, so it's using the fixed [Double-checked locking](https://en.wikipedia.org/wiki/Double-checked_locking#Usage_in_Java) available in Java 5. – Petr Jul 14 '13 at 19:01
-
If there are more `lazy` variables, is there a separate boolean flag for each of them? Or does Scala use some bit flags in such a case? – Petr Jul 14 '13 at 19:02
-
2Updated the answer with another example containing three lazy variables. If you want to know what happens when it exceeds a byte, you can try it yourself ;) – Chris Martin Jul 14 '13 at 19:30
-
Oh, and thanks for identifying this as double-checked locking. I learned something today. – Chris Martin Jul 15 '13 at 00:09
-
@bwawok The first check is in `a()` before the lock. The second check is in `a$lzycompute()` after the lock. – Chris Martin Jul 16 '13 at 01:21
-
@bwawok As far as I can tell the code we can see above only synchronizes when the value hasn't been computed yet. The flag is volatile so no need for a synchronised block. – Machisuji Jul 16 '13 at 06:12
0
Update Scala 2.12.1 (Dec. 2016, three years later).
After PR 5294 (Fields phase fully expands lazy vals and modules), you can read in commit 743f0d2:
Lazy val without local.
Now
synchronized
is erased specially to avoid boxing, we can drop that work around.Note that this does add an extra cast and getter call on the slow path, but that likely doesn't matter.
class C { def foo = {lazy val x = {println("a"); "A" }; x } }
becomes:
def foo(): String = {
lazy <artifact> val x$lzy: scala.runtime.LazyRef[String] = new scala.runtime.LazyRef[String]();
<artifact> private def x$lzycompute(): String =
x$lzy.synchronized[String]{
if (x$lzy.initialized())
x$lzy.value() // NOTE: gets an `.asInstanceOf[String]` after erasure
else
{
x$lzy.value_=({
scala.Predef.println("a");
"A"
});
x$lzy.initialized_=(true);
x$lzy.value() // NOTE: gets an `.asInstanceOf[String]` after erasure
}
}
lazy def x(): String =
if (x$lzy.initialized())
x$lzy.value() // NOTE: gets an `.asInstanceOf[String]` after erasure
else
x$lzycompute();
x()
}