Java最终字段:is";污点;当前JLS可能出现的行为
我现在正在努力理解 为了更好地理解JLS中的文本,我还阅读了Jeremy Manson(JMM的创建者之一)的文章 这篇文章包含了一个让我感兴趣的例子:如果一个带有最终字段的对象对另一个线程可见两次:Java最终字段:is";污点;当前JLS可能出现的行为,java,multithreading,final,java-memory-model,jls,Java,Multithreading,Final,Java Memory Model,Jls,我现在正在努力理解 为了更好地理解JLS中的文本,我还阅读了Jeremy Manson(JMM的创建者之一)的文章 这篇文章包含了一个让我感兴趣的例子:如果一个带有最终字段的对象对另一个线程可见两次: 在o的构造函数完成之前,首先“不正确” o的构造函数完成后的下一步“正确” 然后,t可以看到半构造的o,即使它仅通过“正确”发布的路径访问 以下是本文的部分: 图7.3:简单最终语义示例 f1是最后一个字段;其默认值为0 线程1 线程2 线程3 o.f1=42p=o冷冻o.f1q=o r1=p
- 在
的构造函数完成之前,首先“不正确”o
的构造函数完成后的下一步“正确”o
t
可以看到半构造的o
,即使它仅通过“正确”发布的路径访问
以下是本文的部分:
图7.3:简单最终语义示例
f1是最后一个字段;其默认值为0
线程1
线程2
线程3
o.f1=42
p=o
冷冻o.f1
q=o
r1=p
i=r1.f1
r2=q
如果(r2==r1)
k=r2.f1代码>
r3=q
j=r3.f1
是,这是允许的
主要在JMM
中已经引用的章节中披露:
假设对象构造“正确”,一旦对象
已构造,指定给
构造函数将对所有其他线程可见,而不显示
同步
对于一个对象来说,正确构造意味着什么?这很简单
表示不允许引用正在构造的对象
施工期间的“逃生”
换句话说,不要放置对对象的引用
在其他线程可以看到的任何地方构造
信息技术不要将其分配给静态字段,也不要将其注册为静态字段
侦听器与任何其他对象,依此类推。这些任务应该完成
在构造函数完成后,而不是在构造函数中**
*
所以,是的,这是可能的,只要允许。最后一段充满了如何不做事情的建议;每当有人说要避免做X,那就意味着X是可以做到的
如果…反射
其他答案正确地指出了其他线程正确查看最终字段的要求,例如构造函数末尾的冻结、链等。这些答案加深了对主要问题的理解,应该先阅读。这一条主要关注这些规则的一个可能例外
这里重复最多的规则/短语可能是这一条,摘自尤金的答案(顺便说一句,不应该投反对票):
当一个对象的构造函数完成时,它被认为是完全初始化的。一个线程,它只能看到对
对象在该对象完全初始化后
保证查看该对象的正确[分配/加载/设置]值
最终字段
注意,我用分配、加载或设置的等效术语更改了术语“initialized”。这是有目的的,因为术语可能会误导我的观点
另一个恰当的说法是chrylis-cautiouslyoptimistic-的说法:
“最终冻结”发生在构造函数的末尾,并从
所有读数上的该点都保证准确。
最终字段语义声明:
在此之后只能看到对象的引用的线程
对象已完全初始化,保证可以看到
正确初始化该对象最终字段的值
但是,你认为反思会对这件事产生负面影响吗?不,当然不是。它甚至没有读那一段
后续修改最终
字段
这些陈述不仅正确,而且有JLS
的支持。我不打算反驳他们,只想补充一些关于这条法律例外情况的额外信息:反射。除其他外,该机制可以在初始化后更改最终字段的值
final
字段的冻结发生在设置了final
字段的构造函数的末尾,这是完全正确的。但是冻结操作还有另一个未被考虑的触发器:冻结最终
字段的也会通过反射()初始化/修改字段:
最后一个字段的冻结发生在
设置最后一个字段,并在每次修改后立即设置
通过反射对最后一个字段进行处理
对final
字段的反射操作“打破”了规则:构造函数正确完成后,final
字段的所有读取仍然不能保证准确。我会尽力解释的
让我们想象一下,所有正确的流都得到了尊重,构造函数已经初始化,实例中的所有final
字段都被线程正确地看到。现在是时候通过反射对这些字段进行一些更改了(想象一下这是必要的,即使不寻常,我知道…)
遵循前面的规则,所有线程等待所有字段被更新:与通常的构造函数场景一样,只有在冻结字段并正确完成反射操作后才能访问字段这就是违反法律的地方:
如果最后一个字段初始化为中的常量表达式()
在字段声明中,可能无法观察到对最终字段的更改,
因为最终字段的使用在编译时被替换为
常量表达式的值
这说明了:即使遵循了所有规则,如果变量为p,代码也无法正确读取final
字段的赋值
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;
public class FinalGuarantee
{
private final int i = 5; //initialized as constant expression
private final long l;
public FinalGuarantee()
{
l = 1L;
}
public static void touch(FinalGuarantee f) throws Exception
{
Class<FinalGuarantee> rfkClass = FinalGuarantee.class;
Field field = rfkClass.getDeclaredField("i");
field.setAccessible(true);
field.set(f,555); //set i to 555
field = rfkClass.getDeclaredField("l");
field.setAccessible(true);
field.set(f,111L); //set l to 111
}
public static void main(String[] args) throws Exception
{
FinalGuarantee f = new FinalGuarantee();
System.out.println(f.i);
System.out.println(f.l);
touch(f);
System.out.println("-");
System.out.println(f.i);
System.out.println(f.l);
}
}
5
1
-
5
111
16: before the update
42: after the update
final boolean flag; // false in constructor
final int x; // 1 in constructor
1- Set flag to true
2- Set x to 100.
while (!instance.flag) //flag changes to true
Thread.sleep(1);
System.out.println(instance.x); // 1 or 100 ?
final boolean flag; // false in constructor
1- Set flag to true
while (!instance.flag) { /*deadlocked here*/ }
/*flag changes to true, but the thread started to check too early.
Compiler optimization could assume flag won't ever change
so this thread won't ever see the updated value. */
o.f1 = 42;
p = o;
freeze o.f1;
q = o;
(hb) (hb)
w ------> freeze, freeze ------> q
r3 = q;
j = r3.f1;
(hb) (hb)
w ------> freeze --> freeze ------> q --> mc(w, r1).
(hb) (hb) (mc) (dereferences)
w ------> freeze -----> a ------> r1 ----------------> r2
hb(w, r2).
w ʰᵇ ► f ʰᵇ ► a ᵐᶜ ► r1 ᵈᶜ ► r2
, where:
o.f1 = 42 ʰᵇ ► { freeze o.f is missing } ʰᵇ ► p = o ᵐᶜ ► r1 = p ᵈᶜ ► k = r2.f1