Java Janguage规范的澄清

Java Janguage规范的澄清,java,jls,Java,Jls,它不应该是myS.equals(“/usr”)in吗 最终字段的设计允许必要的安全保证。 考虑下面的例子。一根线(我们称之为 线程1)执行 另一个线程(线程2)执行时 字符串对象是不可变的,字符串操作是不可变的 不执行同步 它们描述了一种假设情况,在这种情况下,由于竞争条件,返回的子字符串可能看起来是/tmp或/usr。因此,使用哪个字符串进行比较并不重要;本例的要点是,如果本例中描述的条件成立,则两者都可能是正确的。事实上,不,这可能是您乍一看的想法,但这是有意的。进一步引用正文(强调我的):

它不应该是
myS.equals(“/usr”)
in吗

最终字段的设计允许必要的安全保证。 考虑下面的例子。一根线(我们称之为 线程1)执行

另一个线程(线程2)执行时

字符串对象是不可变的,字符串操作是不可变的 不执行同步


它们描述了一种假设情况,在这种情况下,由于竞争条件,返回的子字符串可能看起来是/tmp或/usr。因此,使用哪个字符串进行比较并不重要;本例的要点是,如果本例中描述的条件成立,则两者都可能是正确的。

事实上,不,这可能是您乍一看的想法,但这是有意的。进一步引用正文(强调我的):

[…]如果String类的字段不是final,那么线程2可能(尽管不太可能)最初会看到字符串对象偏移量的默认值0,允许它与“/tmp”进行比较


我看不出有什么不对。这两个代码片段都指向不同的线程,并以实例来证明java中String类的真正不变性。前面,当线程2正在执行时,字符串的值是“/THREADS…”,然后线程1将其更改为“/usr”。说明中对其进行了明确解释。

不,不应该这样做

这个例子很好,因为它提到了一些JVM实现中存在的一个bug(不幸的是,我不记得是哪个)<代码>字符串。子字符串没有创建新字符串,而是指向旧字符串,非最终的
偏移量
长度
字段指向正确的位置。但是,由于字段不是最终字段(并且没有其他同步),因此可能发生的情况正是示例代码后面的段落中提到的情况:

特别是,如果String类的字段不是final,那么它是可能的 (尽管不太可能)线程2最初可以看到偏移量的默认值0 字符串对象的值,允许它与“/tmp”进行比较。之后的一次手术 String对象可能会看到正确的偏移量4,因此String对象被视为 是“/usr”


所以事实上,虽然字符串被认为是不可变的,但它们并不是不可变的,因为在它们的构造函数完全运行之前,对象对其他线程是可见的。这个例子说明了这一点。

String
s在内部是一个具有偏移量和长度的字符数组。此字符数组过去在多个字符串之间重复使用。作为内存使用优化,
substring()
将返回一个新的
字符串
,该字符串由与原始字符串相同的字符数组支持。此方法所做的是确定结果字符串进入此字符数组的新偏移量和长度,然后调用私有构造函数创建此结果对象并返回它

(正如Joachim指出的,这不再是
String
的工作方式,而是JLS中的示例基于较旧的内部结构。)

现在,这个私有构造函数的实现,JIT或CPU的指令重新排序,或者线程间共享内存的古怪工作方式,都可能导致这个新的
String
对象首先将其长度设置为
4
。偏移量将保持在
0
,使该
字符串的值成为“/tmp”
。只有一秒钟之后,其偏移量才会设置为
4
,并正确地将其值设置为
“/usr”


另一种方法是:线程2将能够在其构造函数的中间执行这个字符串的观察。这似乎是违反直觉的,因为人们直观地理解线程1的代码是首先完全执行赋值的右侧,然后才更改

Global.s
的值。不幸的是,如果没有适当的内存同步,其他线程可以观察到不同的事件序列。使用
final
字段是使JVM正确处理此问题的一种方法。(我相信将
Global.s
声明为
volatile
也会起作用,但我不是100%确定。)

请注意:它们不再以这种方式实现。现在,每个
字符串
都有自己的
char[]
并且没有偏移量或长度字段)。请看。@JoachimSauer谢谢,我将在中编辑它。可以准确地说,这种遗留行为是JLS中声明的解释,对吗?(即使那个具体的例子不再有效)我很确定这就是原因。
Global.s = "/tmp/usr".substring(4);
String myS = Global.s;
if (myS.equals("/tmp"))System.out.println(myS);