scala里的lazy变量,背后的实现是个典型的双重检测锁(DCL)模式。比如:
class A{
lazy val a=2
}
对a的访问,先通过状态标记变量(做一次位与操作)判断是否已经初始化过。通过scalac -Xprint:jvm
来看对a的访问:
lazy private[this] var a: Int = _;
// 通过标记变量的与1做位与操作,判断是否已初始化
lazy def a(): Int = if (A.this.bitmap$0.&(1).$asInstanceOf[Byte]().==(0))
A.this.a$lzycompute()
else
A.this.a;
看看状态变量(需要用volatile修饰) 和延迟计算的逻辑:
// 定义一个可变的Byte类型的变量 bitmap$0
@volatile private[this] var bitmap$0: Byte = 0.$asInstanceOf[Byte]();
// 延迟计算的函数
private def a$lzycompute(): Int = {
{
// 线程安全
A.this.synchronized({
// 按位与操作,
if (A.this.bitmap$0.&(1).$asInstanceOf[Byte]().==(0))
{
A.this.a = 2;
// 改变 bitmap$0 的值,标记a已经赋值过了
A.this.bitmap$0 = A.this.bitmap$0.|(1).$asInstanceOf[Byte]();
()
};
scala.runtime.BoxedUnit.UNIT
});
()
};
A.this.a
};
为什么用 Byte 变量而非用Boolean,是当有多个lazy成员时,可以用同一个状态变量,按”位”标记,而不必用多个Boolean变量来标识各个lazy成员的状态。
比如有a,b,c 三个lazy成员,则 bitmap$0 第一位表示a,第二位表示b,第三位表示c,以此类推;判断时只要分别与1,2,4,8… 做位与操作就知道了。
不过Byte类型最大只有8位,一个类中如果超过8个lazy成员,编译器就把 bitmap$0 状态变量改用Int类型了。
学习了,感谢分享!
居然是对lazy量的拥有者加锁啊,锁粒度挺大的
在类中只有一个lazy成员时,使用Boolean,但是有多个的时候,才会使用Byte