Java双重检查锁定 – 字符串

鉴于string s包含final字段,它是否意味着在双重检查锁定的上下文中,没有必要声明它们是volatile ? 例如

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

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

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

这不适用于其他物体。 以这个小class为例:

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

当你有这个类的最终对象时,并不意味着没有人可以改变对象内的字段。 你不能将其他东西分配给最终变量!

结论:访问final String时,根据Object本身的不同,访问最终的Objects时可能不需要同步。

不,你仍然必须在这里声明val为volatile。 问题是虽然String是不可变的并且线程安全,但val不是。 val本身仍然存在可见性问题。

为了解决关于“给定String包含最终字段”的观点,请注意JLS特别指出在处理final字段时可见性不可传递。

给定写入w,冻结f,动作a(不是最终字段的读取),由f冻结的最终字段的读取r1,以及读取r2使得hb(w,f),hb( f,a),mc(a,r1)和解引用(r1,r2),然后当确定r2可以看到哪些值时,我们考虑hb(w,r2)。 (这种情况发生 – 在订购之前不会与其他发生的顺序关闭。)

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

其中“冻结f”是JLS如何引用final字段语义的线程安全部分,即实际使该字段引用的对象可见的部分。

(在某些情况下,你可以依赖传递与同步之前发生 .Brian Goetz称之为’背负’并在Java Concurrency in Practice中讨论它但它仅仅是专家,我不建议直到你成为Java内存模型的专家。)

简而言之,声明val volatile并且不用担心通过跳过同步来节省两纳秒。 代码中额外的严谨性是不值得的,它无论如何都不起作用。