根据Java内存模型,在对象的构造函数中初始化的final
字段不受进一步修改,保证每个读取它的线程都能正确看到它的值,即使该对象本身已经发布了数据竞赛。
JLS谈到17.5.3 Subsequent Modification of Final Fields,并含糊地说明
实现可以提供在最终字段安全上下文中执行代码块的方法。
它似乎并没有真正定义此类修改的语义,也不确定这个最终字段安全上下文事物必须存在或如何定义它(即,JLS似乎没有给出关于后续修改最终字段的任何保证。)
我必须说我没有完全理解部分订单 dereferences()和 mc(),也没有完全理解冻结的行为在对最终字段进行任何修改之后发生的操作(归因于它的初始值或后续修改)。
在这个上下文中,我想知道的是:如何(像)Gson这样的(de)序列化框架能够保证包含在构造函数中正确初始化的最终字段的反序列化对象不会造成线程可见性问题吗? /强>
例如,考虑这个类:
class X {
private final String s;
public X(final String s) { this.s = s; }
@Override public String toString() { return s; }
}
以下代码:
final Gson gson = new Gson();
X x = gson.fromJson(gson.toJson(new X("abc")), X.class);
System.out.println(x);
// prints abc
使用调试器进入方法fromJson
,我看到sun.misc.Unsafe
用于分配X
的实例而不调用其构造函数,字段为setAccessible(true)
,最后他们得到了设定。
这只适用于Sun的(或兼容的)JVM!看起来Gson也有特定于多个Android版本的代码。
那么,是否存在与这些反序列化的最终字段相关联的任何线程安全保障,就像我使用X
构建的new X("abc")
实例一样?如果是这样,此保证来自哪里?
谢谢!
答案 0 :(得分:1)
线程安全
在我阅读它时,线程安全保证来自于给定属性被声明为final的事实。它不是线程安全的点是:
final
属性分配值之前final
字段(即,当值正在被修改时,以及在此过程完成之前)这里需要注意的是,您链接的引用允许存在final
字段修改能力)。
<强>冷冻强>
最后一个字段的冻结都发生在构造函数的末尾 最后一个字段设置,并在每次修改后立即 通过反射或其他特殊机制获得最终场地。
就冻结调用而言,基本上它是说freeze
用于将属性标记为“这不能改变”,并且它会这样做:
final
字段赋值final
字段值更改后
线程安全问题仅适用于到正在修改/反序列化的对象。
<强>所以:强>
(...code that comes before...)
END final field safe context - entering non-threadsafe area
final fields are not frozen
code that deserializes an object OR modifies a final field via reflection
final fields are re-frozen
RESUME final field safe context - re-entering threadsafe area
(...code that comes after...)
<强>因此... 强>
构造函数运行后(即,您已经获得了一个分配了值的实例化对象),最终字段无法更改,因为它被冻结,除非使用Reflection API直接更改该值 - 之后它再次被冻结,因为毕竟该领域被宣布为最终。
如果您正在寻找特定于Android的答案(为了确保其JVM行为与Sun的/ Oracle JVM一致),您需要找到该JVM的等效文档。
答案 1 :(得分:1)
解冻伴随着(java.reflect。)Field.setAccessible(true)调用。大多数使用反射定期设置最终字段的框架在成功修改后通常不会调用field.setAccessible(false),因此将此字段“解冻”。
因此,任何其他负责的反射框架都会看到该字段是可访问的,并且可能会这样做。这是一个理论上的机会,但它可能会发生。序列化机制中的这种操作使用类Unsafe(sun实现包)的方法是有原因的。
因此,当使用任何类型的反射API时,您可能真的搞乱了JVM并导致任何类型的不可预测的行为。保存默认反序列化机制的唯一方法是它在实例创建时完成,没有机会存在任何并发访问。
这就是为什么SecurityManager可以限制甚至禁止这种访问。
答案 2 :(得分:0)
来自JLS
最终字段的冻结既发生在设置了最终字段的构造函数的末尾,也发生在通过反射或其他特殊机制对每个最终字段进行修改之后。
因为记忆效应仅在freeze
动作的术语中定义,这意味着如果在构造函数之后修改final
字段,语义也适用 - 只要对象引用没有泄漏那个。这被认为是一个合法的用例。
一旦发布了对象引用,对最终字段的进一步修改就不是一个好主意。