【问题标题】:Clarification from Java Janguage SpecificationJava语言规范的澄清
【发布时间】:2013-09-19 12:44:06
【问题描述】:

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

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

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

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

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

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

【问题讨论】:

    标签: java jls


    【解决方案1】:

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

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

    【讨论】:

      【解决方案2】:

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

      【讨论】:

        【解决方案3】:

        我看不出有什么不对。代码sn-ps都指向不同的线程,并举例说明java中String类的真正不变性。之前,线程2执行时字符串的值为“/THREADS....”,然后被线程1改成“/usr”,描述中解释的很清楚。

        【讨论】:

          【解决方案4】:

          不,不应该。

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

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

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

          【讨论】:

            【解决方案5】:

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

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

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

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

            【讨论】:

            • 请注意:它们不再以这种方式实现。现在每个String 都有自己的char[] 并且没有偏移或长度字段)。见this question for details
            • @JoachimSauer 谢谢,我会编辑它。仍然准确地说这种遗留行为是对 JLS 中声明的解释,对吧? (即使该特定示例不再有效。)
            • 我很确定这就是(仍然)原因。