关于 JLS 3, 17.5 Final Field Semantics 的第二讨论部分:http://java.sun.com/docs/books/jls/third_edition/html/memory.html#17.5
据说myS可以等于“/tmp”,我个人对此无法理解。任何人都可以给予更多解释吗?还有一点是这个例子告诉我们的,这是否意味着如果我们想在多线程之间共享Global.s,我们需要将其设为final(如果是final,则构建后不能更改)或者需要在读写时同步?或者声明一个长度为 1 且为 Final 的 String 数组,以便可以更改和共享?
JLS中的原始内容:
<小时/>考虑以下示例。一个线程(我们将其称为线程 1)执行
Global.s = "/tmp/usr".substring(4);
当另一个线程(线程2)执行时
String myS = Global.s;
if (myS.equals("/tmp"))System.out.println(myS);
<小时/>
JLS中的解释:
字符串对象是不可变的,字符串操作不执行同步。虽然 String 实现不存在任何数据争用,但其他代码可能存在涉及使用 String 的数据争用,并且内存模型对存在数据争用的程序的保证较弱。特别是,如果 String 类的字段不是最终的,那么线程 2 最初有可能(尽管不太可能)看到字符串对象偏移量的默认值 0,从而允许它比较等于“/tmp”。稍后对 String 对象的操作可能会看到正确的偏移量 4,因此 String 对象被视为“/usr”。 Java 编程语言的许多安全功能都依赖于字符串被视为真正不可变的,即使恶意代码使用数据竞争在线程之间传递字符串引用也是如此。
最佳答案
字符串是使用 char[]、偏移量和计数来实现的。 substring 方法使用相同的 char[] 以及新的偏移量和计数构造一个新的 String 对象。根据执行语义,当线程 2 尝试访问新 String 时,它可能会被部分初始化。根据the source , substring 返回一个用简单构造函数构造的新 String 对象:
644 // Package private constructor which shares value array for speed.
645 String(int offset, int count, char value[]) {
646 this.value = value;
647 this.offset = offset;
648 this.count = count;
649 }
因此,如果不在 String 类定义中将 char[]、offset 和 count 标记为 Final,那么线程 2 在访问它们时可能会看到不一致的值。如果发生这种情况,则可以设置 char[],但偏移量和计数可能是错误的。如果 offset 仍然显示为默认值 0,您将看到整个字符串。当然,这需要惊人的时机、JIT 对指令的具体重新排序以及大量的“运气”才能实现这一目标。
关于java - 为什么以下程序可以在 JVM 内存模型的 JLS 规范下运行,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6638819/