java - 为什么以下程序可以在 JVM 内存模型的 JLS 规范下运行

标签 java jvm

关于 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/

相关文章:

java - 打印大数字而不显示 e 符号

java - 用限制替换所有非数字

jvm - jvm 如何以及何时何地更改 Linux 的最大打开文件值?

java - Xerces - 从字符串加载模式

java - 检查列表中子类的实例并返回对象

java - 循环没有重置?

java - python neo4j jvm错误

java - 使用 Swing 时 Eden 空间使用量不断增加

java - JVM如何创建抽象类的对象?

gradle - Kotlin 是否支持 Java 11?