关于JLS 3,17.5最后字段语义的第二部分:编辑/html/Memy.html#17.5
据说myS可以等于"/tmp",我个人无法理解这一点。有人能给出更多的解释吗?另一点是这个例子告诉我们的,它是否意味着如果我们想要在多线程之间共享Global.s,我们需要使它成为最终的(如果它是最终的,那么在构造后不能更改)还是需要在读和写时同步?或者声明长度为1和final的字符串数组,以便可以更改和共享??
JLS的原始内容:
考虑下面的例子。执行一个线程(我们称之为线程1)。
Global.s = "/tmp/usr".substring(4);当另一个线程(线程2)执行时
String myS = Global.s;
if (myS.equals("/tmp"))System.out.println(myS);在JLS中的解释:
字符串对象是不可变的,字符串操作不执行同步。虽然字符串实现没有任何数据竞争,但其他代码可能有涉及String使用的数据竞争,而内存模型为具有数据竞争的程序提供了弱的保证。特别是,如果String类的字段不是最终字段,那么Thread 2可以(虽然不太可能)看到字符串对象偏移量的默认值0,从而允许它与"/tmp“进行比较。以后对String对象的操作可能会看到正确的偏移量4,因此字符串对象被认为是"/usr“。Java编程语言的许多安全特性依赖于String被认为是真正不可变的,即使恶意代码正在使用数据竞赛在线程之间传递字符串引用。
发布于 2011-07-10 02:57:28
字符串使用char[]、偏移量和计数来实现。子字符串方法使用相同的char[]构造一个新的String对象,并构造一个新的偏移量和计数。根据执行语义,当线程2试图访问新字符串时,可以部分初始化新字符串。根据来源,子字符串返回一个由简单构造函数构造的新字符串对象:
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 }因此,在不标记char[]、偏移量和计数为String类定义的情况下,线程2在访问它们时可能会看到不一致的值。如果发生这种情况,则可以设置char[],但偏移量和计数可能是错误的。如果偏移量仍然显示为默认的0,则会看到整个字符串。当然,要做到这一点,需要惊人的时间、JIT对指令的具体重新排序以及大量的“运气”。
发布于 2011-07-10 02:46:43
这取决于"/tmp/usr".substring(4)何时执行。
Global.s = "/tmp/usr".substring(4);实际执行为:
Global.s = "/tmp/usr";
s = s.substring(4);如果线程2在代码执行之前查找Global.s,或者(非常不可能)在这两行之间查找,它将看到s不是"/usr/",而是null或"/tmp/usr",这取决于时间。
字符串是不可变的,但是对String的引用不是:
String str = "A"; // "A" is immutable
str = "B"; // variable "str" may be changed to refer to a different String发布于 2011-07-10 09:53:53
您需要在上下文中阅读该示例和说明。上下文是,它解释了为什么Java定义final字段具有与内存模型相关的特殊语义。
它的要点是,如果没有特殊的final语义,一个线程可以看到另一个线程在不一致的状态下创建的不可变对象。特殊的final语义阻止了这一点;请参见JLS 17.5.1。在本例中,String类的3个内部字段是final,这意味着字符串构造函数的结束与结果字符串上的任何操作之间存在先于前的关系。
但是JLS说它可以是"/tmp",我无法理解。
子字符串操作的目标是创建状态为{offset: 4, count: 4, chars: "/tmp/usr".chars}的String对象。
但是内存模型并不保证一个不同步的线程会看到字段按它们的顺序更新。特别是,它可能会看到状态{offset: 0, count: 4, chars: "/tmp/usr".chars}中的字符串.即"/tmp“。
(实际上,这并不是因为final字段的特殊语义所致,就像下面的示例所描述的那样)。
https://stackoverflow.com/questions/6638819
复制相似问题