Java final字段:是"taint"当前JLS可能的行为 [英] Java final fields: is "taint" behavior possible with the current JLS

查看:61
本文介绍了Java final字段:是"taint"当前JLS可能的行为的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我目前正在尝试了解此JLS关于最终字段的部分.

为了更好地理解JLS中的文本,我还在阅读 Jeremy Manson(JMM的创建者之一)的Java内存模型.

To understand the text in the JLS better I'm also reading The Java Memory Model by Jeremy Manson (one of creators of the JMM).

本文包含的示例引起了我的兴趣:如果使具有最终字段的对象 o 对另一个线程 t 两次可见:

The paper contains the example that got me interested: if an object o with final fields is made visible to another thread t twice:

    首先,不当"在 o 的构造函数完成之前接下来,适当地"使用在 o 的构造函数完成之后
  • first "improperly" before o's constructor finishes
  • next "properly" after o's constructor finishes

然后 t 可以看到半结构化的 o ,即使仅通过适当"访问也是如此.已发布的路径.

then t can see semi-constructed o even when it is accessed only via a "properly" published path.

这是论文的一部分:

图7.3:简单的最终语义示例

Figure 7.3: Example of Simple Final Semantics

f1是最后一个字段;其默认值为0

f1 is a final field; its default value is 0

<身体>
线程1 线程2 线程3
Thread 1 Thread 2 Thread 3
o.f1 = 42;
p = o;
freeze o.f1;
q = o;

r1 = p;
i = r1.f1;
r2 = q;
if (r2 == r1)
k = r2.f1;

r3 = q;
j = r3.f1;



我们假设r1,r2和r3看不到空值.i和k可以为0或42,j必须为42.

We assume r1, r2 and r3 do not see the value null. i and k can be 0 or 42, and j must be 42.

请考虑图7.3.我们不会以对最终字段多次写入的复杂性开始.目前,冻结只是在构造函数末尾发生的事情.尽管 r1 r2 r3 可以看到 null 值,但我们不会为此担心.只会导致空指针异常.

Consider Figure 7.3. We will not start out with the complications of multiple writes to final fields; a freeze, for the moment, is simply what happens at the end of a constructor. Although r1, r2 and r3 can see the value null, we will not concern ourselves with that; that just leads to a null pointer exception.

...

在线程2中读取 q.f1 怎么样?是否可以保证在最后一个字段中看到正确的值?编译器可以确定 p q 指向相同的对象,因此可以对 p.f1 重复使用相同的值该线程的q.f1 .我们希望允许编译器在可能的情况下删除对最终字段的多余读取,因此我们允许 k 看到值0.

What about the read of q.f1 in Thread 2? Is that guaranteed to see the correct value for the final field? A compiler could determine that p and q point to the same object, and therefore reuse the same value for both p.f1 and q.f1 for that thread. We want to allow the compiler to remove redundant reads of final fields wherever possible, so we allow k to see the value 0.

将其概念化的一种方法是,如果某个对象读取了对该对象的错误发布的引用,则认为该对象被线程污染"了.如果某个对象被线程污染,则永远不能保证该线程看到该对象的正确构造的最终字段.通常,如果线程 t 读取了对对象 o 的错误发布的引用,则线程 t 永远会看到 o ,不能保证为 o 的最后字段看到正确的值.

One way to conceptualize this is by thinking of an object being "tainted’ for a thread if that thread reads an incorrectly published reference to the object. If an object is tainted for a thread, the thread is never guaranteed to see the object’s correctly constructed final fields. More generally, if a thread t reads an incorrectly published reference to an object o, thread t forever sees a tainted version of o without any guarantees of seeing the correct value for the final fields of o.

我试图在当前的JLS 任何明确允许或禁止这种行为的东西,但我发现的是:

I tried to find in the current JLS anything that explicitly allows or forbids such behavior, but all I found is that:

当对象的构造函数完成时,就认为该对象已完全初始化.保证只有在对象完全初始化之后才能看到对对象的引用的线程才能确保看到该对象的最终字段的正确初始化值.

An object is considered to be completely initialized when its constructor finishes. A thread that can only see a reference to an object after that object has been completely initialized is guaranteed to see the correctly initialized values for that object's final fields.

当前的JLS是否允许这种行为?

推荐答案

是的,允许这种行为.

事实证明,可以在最终字段语义的新表示/描述.

Turns out that a detailed explanation of this same case is available on the personal page of William Pugh (yet another JMM author): New presentation/description of the semantics of final fields.

简短版本:

  • 部分17.5.1.JLS的最终字段语义为最终字段定义特殊规则.
    这些规则基本上使我们能够在构造函数中的最后一个字段的初始化与另一个线程中的该字段的读取之间建立附加的事前关系,即使该对象是通过数据竞争发布的.
    这种额外的先发生后关系要求从字段初始化到在另一个线程中对其进行读取的每个 路径都包含特殊的动作链:

  • section 17.5.1. Semantics of final Fields of JLS defines special rules for final fields.
    The rules basically lets us establish an additional happens-before relation between the initialization of a final field in a constructor and a read of the field in another thread, even if the object is published via a data race.
    This additional happens-before relation requires that every path from the field initialization to its read in another thread included a special chain of actions:

w  ʰᵇ ► f  ʰᵇ ► a  ᵐᶜ ► r1  ᵈᶜ ► r2, where:

  • w 是对构造函数中final字段的写操作
  • f 是冻结动作",在构造函数退出时发生
  • a 是对象的发布(例如,将其保存到共享变量中)
  • r₁是在另一个线程中读取对象的地址
  • r_2 是与r₁相同的线程中最后一个字段的读取.
    • w is a write to the final field in a constructor
    • f is "freeze action", which happens when constructor exits
    • a is a publication of the object (e.g. saving it to a shared variable)
    • r₁ is a read of the object's address in a different thread
    • r₂ is a read of the final field in the same thread as r₁.
    • 问题中的代码具有从 o.f1 = 42 k = r2.f1; 的路径,其中不包含必需的冻结动作:

      the code in the question has a path from o.f1 = 42 to k = r2.f1; which doesn't include the required freeze o.f action:

      o.f1 = 42  ʰᵇ ► { freeze o.f is missing }  ʰᵇ ► p = o  ᵐᶜ ► r1 = p  ᵈᶜ ► k = r2.f1

      因此, o.f1 = 42 k = r2.f1 的发生顺序不正确-⇒在进行数据竞争之前, k= r2.f1 可以读取0或42.

      As a result, o.f1 = 42 and k = r2.f1 are not ordered with happens-before ⇒ we have a data race and k = r2.f1 can read 0 or 42.

      来自新的表示形式/语义描述最终字段:

      为了确定是否可以保证读取最后一个字段来查看该字段的初始化值,您必须确定没有办法构造偏序ᵐᶜ►和ᵈᶜ►而不提供链 w ʰᵇ f ʰᵇ a ᵐᶜr₁ ᵈᶜ r2 从该字段的写入到该字段的读取.

      In order to determine if a read of a final field is guaranteed to see the initialized value of that field, you must determine that there is no way to construct the partial orders  ᵐᶜ ► and  ᵈᶜ ► without providing the chain w  ʰᵇ f  ʰᵇ a  ᵐᶜ r₁  ᵈᶜ r₂ from the write of the field to the read of that field.

      ...

      p 在线程1中的写入和在线程2中的读取涉及一个存储链. q 的线程1中的写入和线程2中的读取也与存储链有关.两次读取 f 都看到相同的变量.从 f 的读取到 p 的读取或 q 的读取之间可能存在解除引用链,因为这些读取具有相同的含义地址.如果取消引用链来自 p 的读取,则不能保证 r5 会看到值42.

      The write in Thread 1 and read in Thread 2 of p are involved in a memory chain. The write in Thread 1 and read in Thread 2 of q are also involved in a memory chain. Both reads of f see the same variable. There can be a dereference chain from the reads of f to either the read of p or the read of q, because those reads see the same address. If the dereference chain is from the read of p, then there is no guarantee that r5 will see the value 42.

      请注意,对于线程2,参考链的顺序为 r2 = p ᵈᶜ r5 = r4.f ,但是不是顺序 r4 = q ᵈᶜ r5 = r4.f .这反映了一个事实,即允许编译器将对对象 o 的最后一个字段的任何读取移动到在其中第一次读取 o 的地址后立即进行的操作.线程.

      Notice that for Thread 2, the deference chain orders r2 = p  ᵈᶜ r5 = r4.f, but does not order r4 = q  ᵈᶜ r5 = r4.f. This reflects the fact that the compiler is allowed to move any read of a final field of an object o to immediately after the the very first read of the address of o within that thread.

      这篇关于Java final字段:是"taint"当前JLS可能的行为的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆