正如我所读到的,Scala不可变val
由于各种原因未被转换为Java final
。这是否意味着必须保护从其他线程访问val
以保证同步,以保证可见性?
答案 0 :(得分:12)
从多线程的角度来看,对val本身的赋值很好,因为在声明它时你必须为val赋值,并且以后不能改变该值(所以如果你做了{{ 1}},s从诞生开始是“你好”:没有线程会读取另一个值)。 然而,有几点需要注意:
1 - 如果您将可变类的实例分配给val,则val本身不会“保护”该类的内部状态不会更改。
val s="hello"
2 - 您(或您的某个库...)可以通过反射更改val。如果发生这种情况,两个线程确实可以为您的val
读取不同的值class Foo(s:String) { var thisIsMutable=s }
// you can then do this
val x = new Foo("hello")
x.thisIsMutable="goodbye"
// note that val guarantees that x is still the same instance of Foo
// reassigning x = new Foo("goodbye") would be illegal
答案 1 :(得分:9)
作为对象成员,初始化后,val
永远不会在对象的生命周期内更改其值。因此,只要对象的引用没有在构造函数中转义,所以保证它们的值对所有线程都可见。事实上,他们获得了Java final
修饰符,如下所示:
object Obj {
val r = 1
def foo {
val a = 1
def bar = a
bar
}
}
使用javap:
...
private final int r;
...
public void foo();
...
0: iconst_1
1: istore_1
2: aload_0
3: iload_1
4: invokespecial #31; //Method bar$1:(I)I
7: pop
...
private final int bar$1(int);
...
0: iload_1
1: ireturn
...
作为方法本地,它们仅在方法中使用,或者它们作为参数传递给嵌套方法或闭包(参见上面提到的bar$1
)。闭包可能会传递给另一个线程,但它只有一个最终字段,其值为本地val
。因此,它们从创建它们到所有其他线程的位置都是可见的,并且不需要同步。
请注意,这并未说明val
指向的对象 - 它本身可能是可变的并且需要同步。
在大多数情况下,不能通过反射来违反上述内容 - Scala val
成员声明实际上会生成一个具有相同名称的getter和一个getter访问的私有字段。尝试使用反射来修改字段将导致NoSuchFieldException
。您可以修改它的唯一方法是在类中添加specialized
注释,这将使专用字段受到保护,从而可以进行反射。我目前无法想到任何其他情况可能会改变声明为val
...