0

鉴于strings 包含 final 字段,这是否意味着在双重检查锁定的上下文中不需要声明它们volatile?例如

class SomeClass{
     private String val;

     String getVal(){
           if(val == null){
                synchronized(this){
                      if(val ==null)
                           val = new String("foo");
                }
          }
     }
}

我以字符串为例,但它应该与声明一些最终字段的其他对象一起使用,对吗?

4

2 回答 2

2

对于字符串,你是对的。声明为 final 的字符串不能有差异,因此在使用它时不需要同步。

对于其他对象,情况并非如此。以这个小班为例:

public class BankAccount {
    private int balance = 0;
    public void addMoney(int money) {
        balance+=money;
    }
}

当您拥有此类的最终对象时,这并不意味着没有人可以更改对象内的字段。您只是不能将其他内容分配给最终变量!

结论:当访问最终字符串时,您不需要同步,当访问最终对象时,您可能必须同步,这取决于对象本身。

于 2015-11-12T13:00:02.293 回答
2

不,您仍然必须在val此处声明为 volatile。问题是 whileString是不可变的和线程安全的,val但不是。您本身仍然存在可见性问题val

为了解决您关于“鉴于 String 包含最终字段”的观点,请注意 JLS 明确表示在处理final字段时可见性不具有传递性。

给定一个写入 w、一个冻结 f、一个动作 a(不是读取最终字段)、读取被 f 冻结的最终字段的 r1 和读取 r2,使得 hb(w, f)、hb( f, a), mc(a, r1) 和 dereferences(r1, r2),那么在确定 r2 可以看到哪些值时,我们会考虑 hb(w, r2)。(这发生在排序之前不会与其他发生在排序之前传递关闭。)

https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.5

其中“freeze f”是 JLS 引用字段语义的线程安全部分的方式final,即实际使字段引用的对象可见的部分。

(在某些情况下,您可以通过synchronizes-withhappens-before来依赖传递性 。Brian Goetz 将此称为“捎带”并在Java 并发实践中讨论过它。 但它几乎只是专家,我不推荐直到你成为 Java 内存模型专家为止。)

简而言之,声明valvolatile 并且不要担心通过跳过同步来节省两纳秒。代码中额外的繁琐是不值得的,而且无论如何也不起作用。

于 2015-11-12T15:02:01.393 回答