【问题标题】:Scala final vs val for concurrency visibilityScala final 与 val 的并发可见性
【发布时间】:2011-11-29 09:48:44
【问题描述】:

在 Java 中,当跨多个线程(通常)使用对象时,最好将字段设置为 final。例如,

public class ShareMe {
    private final MyObject obj;
    public ShareMe(MyObject obj) {
        this.obj = obj;
    }
}

在这种情况下,obj 的可见性将在多个线程中保持一致(假设 obj 也具有所有 final 字段),因为它是使用 final 关键字安全地构造的。

在 scala 中,似乎 val 不会编译为最终引用,而是 val 是 scala 中的语义,它阻止您重新分配变量 (Scala final variables in constructor)。如果 scala 构造函数变量未定义为 final,它们是否会遇到同样的问题(在 actor 中使用这些对象时)?

【问题讨论】:

    标签: scala concurrency visibility final actor


    【解决方案1】:

    另一个问题的答案具有误导性。 final 一词有两种含义:a) 对于 Scala 字段/方法和 Java 方法,它表示“不能在子类中被覆盖”,b) 对于 Java 字段和 JVM 字节码,它表示“该字段必须在构造函数并且不能被重新分配”。

    val 标记的类参数(或者,等效地,没有修饰符的案例类参数)在第二种意义上确实是最终的,因此是线程安全的。

    这是证据:

    scala>  class A(val a: Any); class B(final val b: Any); class C(var c: Any)
    defined class A
    defined class B
    defined class C
    
    scala> import java.lang.reflect._
    import java.lang.reflect._
    
    scala> def isFinal(cls: Class[_], fieldName: String) = {
         |   val f = cls.getDeclaredFields.find(_.getName == fieldName).get
         |   val mods = f.getModifiers
         |   Modifier.isFinal(mods)
         | }
    isFinal: (cls: Class[_], fieldName: String)Boolean
    
    scala> isFinal(classOf[A], "a")
    res32: Boolean = true
    
    scala> isFinal(classOf[B], "b")
    res33: Boolean = true
    
    scala> isFinal(classOf[C], "c")
    res34: Boolean = false
    

    或者使用javap,可以方便地从 REPL 运行:

    scala> class A(val a: Any)
    defined class A
    
    scala> :javap -private A
    Compiled from "<console>"
    public class A extends java.lang.Object implements scala.ScalaObject{
        private final java.lang.Object a;
        public java.lang.Object a();
        public A(java.lang.Object);
    }
    

    【讨论】:

    • 感谢您的澄清。你能看到我对那个问题的更新吗?对于没有修饰符的非 case 类,我还看到声明为 final 的构造函数变量(如 javap 中所示)。
    • @retronym,我来到你的答案试图找到我的问题的答案,Immutability and thread-safety in Scala。如您所知,Java 中的 final 关键字也用于避免 ctor 指令的重定位,如 this 链接中所述。所以,如果我理解正确你的答案,在 Scala 类中使用 val 字段等于在 Java 类中声明 final 字段,让 JMM 正常工作?
    【解决方案2】:

    我想我可能误解了 var 是如何编译的。我创建了示例类

    class AVarTest(name:String) {
       def printName() {
         println(name)
       }
    }
    

    我跑了javap -private,结果是

    public class AVarTest extends java.lang.Object implements scala.ScalaObject{
        private final java.lang.String name;
        public void printName();
        public AVarTest(java.lang.String);
    }
    

    而name实际上是编译成final的。

    这也显示在Scala val has to be guarded with synchronized for concurrent access?

    【讨论】:

    • 请注意,从 2.9.0.1(可能更早)开始,您最终使用字段名称的唯一原因是您在 printName 中引用了它。如果没有方法引用 ctor 参数,则不再生成字段。
    猜你喜欢
    • 1970-01-01
    • 2012-07-08
    • 2010-12-26
    • 2013-12-23
    • 2014-06-01
    • 2011-03-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多