lazy变量与双重检测锁(DCL)

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变量与双重检测锁(DCL)》上有1条评论

  1. 胡东卿

    学习了,感谢分享!

    居然是对lazy量的拥有者加锁啊,锁粒度挺大的

    回复

发表评论

电子邮件地址不会被公开。 必填项已用*标注