最终字段语义&使用 setAccessible(true) 进行反序列化
根据 Java 内存模型,在对象的构造函数中初始化且不受进一步修改的 final
字段保证其值能够被每个读取它的线程正确地看到,即使该对象本身已通过以下方式发布:数据竞赛。
JLS 讨论 17.5.3 Final 的后续修改字段,并含糊地指出
实现可以提供一种在最终字段安全上下文中执行代码块的方法。
它似乎并没有真正定义此类修改的语义,也没有确切地定义这个最终字段安全上下文事物必须存在的位置或如何定义它(即,JLS似乎没有给出任何保证最终字段的后续修改)。
我必须说,我没有完全理解偏序 dereferences() 和 mc(),也没有完全理解采取的 freeze 操作的行为放置在对最终字段进行任何修改(归因于其的初始值或后续修改)之后。
在这种情况下,我想知道的是:(反)序列化框架(例如 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
使用调试器进入方法 fromJson
,我看到 sun.misc.Unsafe
用于分配一个实例X
没有调用其构造函数,并且字段是 setAccessible(true)
,最后它们被设置。
这仅适用于 Sun 的(或兼容的)JVM!看起来 Gson 也有针对多个 Android 版本的代码。
那么,是否存在与这些反序列化的最终字段相关的任何线程安全保证,就像我对使用 new X("abc “)?如果是这样,这个保证从何而来?
谢谢!
According to the Java Memory Model, a final
field initialized in the object's constructor not subject to further modifications is guaranteed to have its value correctly seen by every thread reading it, even if the object itself has been published with data races.
The JLS talks about 17.5.3 Subsequent Modification of Final Fields, and vaguely states that
An implementation may provide a way to execute a block of code in a final field safe context.
It does not seem to really define the semantics of such modifications, nor where exactly this final field safe context thing must exist or how to define it (ie, the JLS doesn't seem to give any guarantee about subsequent modification of final fields).
I must say that I did not fully understood the partial orders dereferences() and mc(), nor the behavior of the freeze action that takes place after any modification to a final field (either the initial value attributed to it or subsequent modifications).
On this context, what I want to know is: how can a (de)serialization framework, such as Gson, guarantee that deserialized objects containing final fields properly initialized in a constructor will pose no thread visibility problem?
For example, consider this class:
class X {
private final String s;
public X(final String s) { this.s = s; }
@Override public String toString() { return s; }
}
And the following code:
final Gson gson = new Gson();
X x = gson.fromJson(gson.toJson(new X("abc")), X.class);
System.out.println(x);
// prints abc
Stepping into the method fromJson
with a debugger, I see that sun.misc.Unsafe
is used to allocate an instance of X
without calling its constructor, and the fields are setAccessible(true)
, and finally they get set.
And this is only in Sun's (or compatible) JVMs! It looks like Gson has code specific to multiple Android versions as well.
So, are there any thread-safety guarantees associated with these deserialized final fields, just like I would have with an instance of X
constructed with new X("abc")
? If so, where does this guarantee come from?
Thanks!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
线程安全
正如我所读到的,线程安全保证来自于给定属性被声明为final的事实。它不是线程安全的点是:
final
属性分配值之前final
字段期间(即,当值正在被修改的过程中,并且在此过程完成之前)这里需要注意的是,引用你链接到允许某事理论上的可能性存在除 Reflection API 之外的其他(但具有相同的
final
字段修改能力)。冻结
至于冻结调用,基本上是说
freeze
用于将属性标记为“无法更改”,它是这样做的:final
字段实际上被分配了一个值final
字段值更改后,通过诸如 Reflection API 之类的方式线程安全问题仅适用到正在修改/反序列化的对象。
所以:
因此...
构造函数运行后(即您已经获得了分配了值的实例化对象),最终字段无法更改,因为它已被冻结,除非使用 Reflection API 直接更改该值 - 此后它会再次冻结,因为毕竟该字段被声明为最终的。
如果您正在寻找特定于 Android 的答案(以确保其 JVM 行为与 Sun/Oracle 的 JVM 一致),则需要找到该 JVM 的等效文档。
Thread safety
As I read it, the thread safety guarantee comes from the fact that a given attribute is declared as final. The point at which it is NOT thread safe, is either:
final
attribute is assigned a valuefinal
field via the Reflection API (i.e. while the value is in the process of being modified, and before it's done with this process)The caveat here is that the reference you linked to allows the theoretical possibility of something other than the Reflection API (but with the same
final
field modification abilities) to exist.Freezing
As far as the freezing calls, basically it's saying that
freeze
is used to mark an attributes as "this cannot be changed", and it does so:final
field is actually assigned a valuefinal
field value change, via something like the Reflection APIThe thread-safety concern only applies to the object being modified/deserialized.
So:
Therefore...
After a constructor runs (i.e. you've got an instantiated object with values assigned), the final field cannot be altered, because it is frozen, except in the case where the Reflection API is used to directly alter that value - after which it is frozen again because, after all, the field is declared final.
If you're looking for an Android-specific answer to this (to ensure that its JVM behavior agrees with Sun's/Oracle's JVM), you'll need to find equivalent documentation for that JVM.
解冻伴随着 (java.reflect.)Field.setAccessible(true) 调用。大多数定期使用反射来设置最终字段的框架通常在成功修改后永远不会调用 field.setAccessible(false) ,因此使该字段保持“未冻结”状态。
因此,任何其他负责的反射框架都会看到该字段是可访问的,并且可能会这样做。这是理论上的机会,但它可能会发生。序列化机制内部的此类操作使用Unsafe类(sun实现包)的方法是有原因的。
因此,当使用任何类型的反射 API 时,您确实可能会弄乱 JVM 并导致任何类型的不可预测的行为。唯一保存默认反序列化机制的是它是在实例创建时完成的,此时不可能发生任何并发访问。
这就是为什么安全管理器可以限制甚至禁止此类访问。
The unfreezing comes along with the (java.reflect.)Field.setAccessible(true) call. Most frameworks using reflection regularly to set final fields often never call field.setAccessible(false) after a successful modification so leaving this field 'unfrozen'.
So any other reflection framework in charge will see that the field is accessible and might do so. This is a theoretical chance but it might occure. There is a reason why such kind of operation inside the serialization mechanism is using a method of the class Unsafe (sun implementation package).
So when using any kind of a reflection API you can really mess up a JVM and cause any kind of unpredictable behaviour. The only thing that saves the default deserialization mechanism is that it is done on instance creation time, where no chance exist that there will be any concurrent access happening.
Thats why such kind of access can be restricted or even forbidden by a SecurityManager.
来自JLS
由于记忆效应仅根据
freeze
操作来定义,这意味着如果在构造函数之后修改final
字段,语义也适用 - 只要对象引用不存在在此之前泄露。这被认为是合法的用例。一旦对象引用被发布,进一步修改 Final 字段就不是一个好主意。
From JLS
since the memory effect is defined only in term of
freeze
action, this implies that semantics also applies if afinal
field is modified after constructor - as long as the object reference isn't leaked prior to that. This is considered a legitimate use case.As soon as the object reference is published, further modification on a final field is not a good idea.