加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 综合聚焦 > 服务器 > 安全 > 正文

为什么不能使每个Scala实例变量成为一个懒惰的初始化变量?

发布时间:2020-12-16 19:05:31 所属栏目:安全 来源:网络整理
导读:除了增加冗长度外,还有其他强大的原因,为什么不应该声明每个实例变量应该被懒惰地初始化? 解决方法 首先:如果一个懒惰的val(如访问一个不存在的外部资源)出现问题,您只会注意到它是第一次访问val,而正常的val会尽快注意到因为正在构建对象.您还可以在延迟
除了增加冗长度外,还有其他强大的原因,为什么不应该声明每个实例变量应该被懒惰地初始化?

解决方法

首先:如果一个懒惰的val(如访问一个不存在的外部资源)出现问题,您只会注意到它是第一次访问val,而正常的val会尽快注意到因为正在构建对象.您还可以在延迟的vals中具有循环依赖性,这将导致类无法正常工作(其中一个可怕的NullPointerExceptions),但您只会在第一次访问所连接的延迟值之一时发现.

所以懒惰的vals使程序不那么确定性,这总是一件坏事.

第二:有一个延迟val的运行时开销.一个懒惰的val目前由一个私有的位掩码(int)在一个类中使用延迟值实现(对于每个延迟值一个位),所以如果你有超过32个惰性值将有两个位掩码等)

为了确保延迟初始化器只能运行一次,当字段被初始化时,存在对位掩码的同步写入,并且每次访问该字段时都会发生易失性读取.现在,x86架构上的易读性非常便宜,但是易失性的写入可能真的很贵.

据我所知,在未来版本的scala中进行优化,但总是会有一个开销来检查该字段是否被初始化,而不是直接访问.例如,懒惰访问的额外代码可能会阻止方法内联.

当然,对于非常小的类,位掩码的内存开销也可能是相关的.

但即使你没有任何性能问题,很明显vals依赖于彼此的顺序,只是按照顺序排列,并使用正常的vals.

编辑:这里是一个代码示例,说明如果您使用延迟vals,您可能会得到的非确定性:

class Test {
  lazy val x:Int = y
  lazy val y:Int = x
}

你可以创建一个这个类的实例没有任何问题,但是一旦你访问x或y,你将得到一个StackOverflow.这当然是一个人为的例子.在现实世界中,你有更长的和不明显的依赖循环.

这是一个Scala控制台会话,它使用:javap来说明延迟val的运行时开销.首先是正常的val:

scala> class Test { val x = 0 }
defined class Test

scala> :javap -c Test
Compiled from "<console>"
public class Test extends java.lang.Object implements scala.ScalaObject{
public int x();
  Code:
   0:   aload_0
   1:   getfield    #11; //Field x:I
   4:   ireturn

public Test();
  Code:
   0:   aload_0
   1:   invokespecial   #17; //Method java/lang/Object."<init>":()V
   4:   aload_0
   5:   iconst_0
   6:   putfield    #11; //Field x:I
   9:   return

}

现在懒惰的val:

scala> :javap -c Test
Compiled from "<console>"
public class Test extends java.lang.Object implements scala.ScalaObject{
public volatile int bitmap$0;

public int x();
  Code:
   0:   aload_0
   1:   getfield    #12; //Field bitmap$0:I
   4:   iconst_1
   5:   iand
   6:   iconst_0
   7:   if_icmpne   45
   10:  aload_0
   11:  dup
   12:  astore_1
   13:  monitorenter
   14:  aload_0
   15:  getfield    #12; //Field bitmap$0:I
   18:  iconst_1
   19:  iand
   20:  iconst_0
   21:  if_icmpne   39
   24:  aload_0
   25:  iconst_0
   26:  putfield    #14; //Field x:I
   29:  aload_0
   30:  aload_0
   31:  getfield    #12; //Field bitmap$0:I
   34:  iconst_1
   35:  ior
   36:  putfield    #12; //Field bitmap$0:I
   39:  getstatic   #20; //Field scala/runtime/BoxedUnit.UNIT:Lscala/runtime/BoxedUnit;
   42:  pop
   43:  aload_1
   44:  monitorexit
   45:  aload_0
   46:  getfield    #14; //Field x:I
   49:  ireturn
   50:  aload_1
   51:  monitorexit
   52:  athrow
  Exception table:
   from   to  target type
    14    45    50   any

public Test();
  Code:
   0:   aload_0
   1:   invokespecial   #26; //Method java/lang/Object."<init>":()V
   4:   return

}

正如你所看到的,正常的val访问器非常短,一定是内联的,而懒惰的val访问器是相当复杂的(最重要的是并发)涉及一个同步块(monitorenter / monitorexit指令).您还可以看到由编译器生成的额外字段.

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读