来自Java语言规范的澄清

时间:2013-09-19 12:44:06

标签: java jls

this explanation from the JLS不应该是myS.equals("/usr")吗?

  

最终字段旨在提供必要的安全保障。   请考虑以下示例。一个线程(我们将其称为   线程1)执行

Global.s = "/tmp/usr".substring(4);
     

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

String myS = Global.s;
if (myS.equals("/tmp"))System.out.println(myS);
     

String对象旨在是不可变的,字符串操作也是如此   不执行同步。

5 个答案:

答案 0 :(得分:4)

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

答案 1 :(得分:4)

实际上没有,这可能是你乍一看的想法,但这是故意的。在文中进一步引用(强调我的):

  

[...]如果String类的字段不是final,则可能(尽管不太可能)线程2最初可以看到字符串对象偏移的默认值0,允许它比较等于“/ tmp”

答案 2 :(得分:1)

我没有看到任何错误。这两个代码片段都指向不同的线程,并且采用了一些示例来证明java中String类的真正不变性。之前,当线程2执行时,字符串的值为“/ THREADS ....”,然后由线程1将其更改为“/ usr”。在说明中清楚地说明了这一点。

答案 3 :(得分:1)

不,不应该。

这个例子很好,因为它引用了一些JVM实现中存在的错误(遗憾的是我不记得哪个)。 String.substring没有创建新字符串,而是指向旧字符串,其中非最终offsetlength字段指向正确的位置。但是,由于字段不是最终的(并且没有其他同步),可能发生的情况恰好是示例代码后面的段落中提到的情况:

  

特别是,如果String类的字段不是final,那么它是可能的   (虽然不太可能)线程2最初可以看到偏移的默认值0   字符串对象,允许它比较等于“/ tmp”。以后的操作   String对象可能会看到4的正确偏移量,因此String对象被视为   是“/ usr”。

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

答案 4 :(得分:1)

String内部是一个字符数组,其中偏移长度。此字符数组曾用于在多个字符串之间重用。作为内存使用优化,substring()将返回由与原始字符串相同的字符数组支持的新String。此方法的作用是确定结果字符串中此字符数组的新偏移量和长度是多少,然后调用私有构造函数来创建此结果对象并将其返回。

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

现在,私有构造函数的实现,JIT或CPU重新排序的指令,或者通常是线程之间共享内存的古怪方式可能会导致这个新的String对象首先出现其长度设置为4偏移量将保留在0,从而生成此String "/tmp"的值。只有一秒钟后,其偏移量才会设置为4,并使其值正确为"/usr"

换句话说: thread 2 能够在其构造函数执行过程中观察到这个字符串。这似乎违反直觉,因为人们直观地将线程1 的代码理解为首先完全执行赋值的右侧,然后才更改Global.s的值。遗憾的是,如果没有适当的内存同步,其他线程就能够观察到不同的事件序列。使用final字段是使JVM正确处理此问题的一种方法。 (我相信将Global.s声明为volatile也可以,但我不是百分百肯定。)