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);