Java 最终字段语义&;使用setAccessible反序列化(true)
根据Java内存模型,在对象的构造函数中初始化的Java 最终字段语义&;使用setAccessible反序列化(true),java,thread-safety,deserialization,final,Java,Thread Safety,Deserialization,Final,根据Java内存模型,在对象的构造函数中初始化的final字段不需要进一步修改,即使对象本身已经发布了数据竞争,每个读取它的线程都能正确地看到它的值 JLS谈到,并含糊地表示 实现可以提供在最终字段安全上下文中执行代码块的方法 它似乎没有真正定义此类修改的语义,也没有确切定义最终字段安全上下文必须存在的位置或如何定义它(即,JLS似乎没有对最终字段的后续修改提供任何保证) 我必须说,我没有完全理解偏序解引用()和mc(),也没有完全理解对最终字段进行任何修改后发生的冻结操作的行为(归因于它的初始
final
字段不需要进一步修改,即使对象本身已经发布了数据竞争,每个读取它的线程都能正确地看到它的值
JLS谈到,并含糊地表示
实现可以提供在最终字段安全上下文中执行代码块的方法
它似乎没有真正定义此类修改的语义,也没有确切定义最终字段安全上下文必须存在的位置或如何定义它(即,JLS似乎没有对最终字段的后续修改提供任何保证)
我必须说,我没有完全理解偏序解引用()和mc(),也没有完全理解对最终字段进行任何修改后发生的冻结操作的行为(归因于它的初始值或后续修改)
在这种情况下,我想知道的是:像Gson这样的(反)序列化框架如何保证包含构造函数中正确初始化的最终字段的反序列化对象不会造成线程可见性问题?
例如,考虑这个类:
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
使用调试器进入JSON的方法,我看到sun.misc.Unsafe
用于分配X
的实例,而不调用其构造函数,字段是setAccessible(true)
,最后它们被设置
这仅在Sun的(或兼容的)JVM中存在!看起来Gson也有针对多个Android版本的代码
因此,是否有与这些反序列化的最终字段相关联的线程安全保证,就像我使用new X(“abc”)
构造的X
实例一样?如果是这样,这项担保从何而来?
谢谢 来自JLS
最终字段冻结发生在设置最终字段的构造函数结束时,以及通过反射或其他特殊机制对最终字段进行每次修改之后
由于记忆效应仅根据freeze
操作定义,这意味着如果在构造函数之后修改final
字段,语义也适用,只要在此之前对象引用没有泄漏。这被认为是一个合法的用例
对象引用发布后,对最终字段进行进一步修改不是一个好主意。线程安全
正如我所读到的,线程安全保证来自这样一个事实:给定的属性被声明为final。它不是线程安全的点是:
- 在反序列化过程中,分配对象内存空间时,但在
final
属性指定值之前
- 在通过反射API修改
final
字段的过程中(即,在修改值的过程中,以及在此过程完成之前)
这里需要注意的是,您链接到的引用允许存在反射API之外的其他的理论可能性(但具有相同的最终
字段修改功能)
冻结
最后一个字段的冻结发生在
设置最终字段,并在每次修改后立即设置
通过反射或其他特殊机制对最后一个场进行控制
至于冻结调用,基本上是说,freeze
用于将属性标记为“这是无法更改的”,它是这样做的:
- 在构造函数执行结束时,未定义的
final
字段实际上被赋值
- 在
最终
字段值更改之后,通过反射API之类的方式
线程安全问题仅适用于正在修改/反序列化的对象
所以:
(...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...)
因此…
构造函数运行后(即,您得到了一个已赋值的实例化对象),最终字段无法更改,因为它已冻结,除非使用反射API直接更改该值,然后再次冻结,因为毕竟该字段已声明为最终字段
如果您正在寻找特定于Android的答案(以确保其JVM行为与Sun/Oracle的JVM一致),则需要找到该JVM的等效文档。解冻与(java.reflect.)字段.setAccessible(true)调用一起进行。大多数定期使用反射来设置最终字段的框架在成功修改后通常从不调用field.setAccessible(false),因此将此字段保留为“未冻结”
因此,负责的任何其他反射框架都会看到该字段是可访问的,并且可能会这样做。这是一个理论上的机会,但它可能会发生。序列化机制中的此类操作之所以使用类Unsafe(sun实现包)的方法是有原因的
因此,当使用任何类型的反射API时,您都可能真正搞乱JVM并导致任何类型的不可预测行为。唯一可以保存默认反序列化机制的是,它在实例创建时完成,而不存在任何并发访问发生的可能性
这就是为什么SecurityManager可以限制甚至禁止这种访问。JVM如何知道在对象创建后字段何时被重新冻结?即,在哪里可以保证线程不会在sun.misc.Unsafe#allocateInstance(Class)
和通过反射设置所有字段之间中断?那么final field safe有多少次