java - 使用 setAccessible(true) 的最终字段语义和反序列化

标签 java thread-safety deserialization final

根据 Java 内存模型,一个 final在对象的构造函数中初始化的字段不受进一步修改的影响,保证每个读取它的线程都能正确看到它的值,即使对象本身已经发布了数据竞争。

JLS 谈论17.5.3 Subsequent Modification of Final Fields , 并含糊地说

An implementation may provide a way to execute a block of code in a final field safe context.

它似乎并没有真正定义这种修改的语义,也没有确切地定义这个 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 版本的代码。

那么,是否有与这些反序列化的最终字段相关的任何线程安全保证,就像我对 X 的实例所做的那样用 new X("abc") 构建?如果是,此保证从何而来?

谢谢!

最佳答案

线程安全

正如我所读,线程安全保证来自于给定属性被声明为最终属性这一事实。它不是线程安全的点是:

  • 反序列化过程中,分配对象内存空间时,但在为final属性赋值之前
  • 期间通过反射 API 修改 final 字段(即,当值处于修改过程中,并且在此过程完成之前)

这里需要注意的是,您链接到的引用允许存在一些其他而不是反射 API(但具有相同的final 字段修改能力)的理论可能性.

卡住

Freezes of a final field occur both at the end of the constructor in which the final field is set, and immediately after each modification of a final field via reflection or other special mechanism.

就卡住调用而言,基本上是说 freeze 用于将属性标记为“无法更改”,它确实这样做了:

  • 在构造函数执行结束时,未定义的 final 字段实际被赋值
  • final 字段值更改后,通过 Reflection 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...)

因此……

在构造函数运行后(即你有一个分配了值的实例化对象),final 字段不能被改变,因为它被卡住,除非使用反射 API 直接改变该值 - 之后它再次被卡住,因为毕竟该字段被声明为最终字段。

如果您正在寻找特定于 Android 的答案(以确保其 JVM 行为与 Sun/Oracle 的 JVM 一致),您需要找到该 JVM 的等效文档。

关于java - 使用 setAccessible(true) 的最终字段语义和反序列化,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7654747/

相关文章:

java - Wifi 管理器和 WiFi 信息

c++ - 单调计数整数的比较安全吗?

c# - 如何设计一个泛型类来反序列化 XML?

c# - JsonSerializerSettings 中指定的错误是否捕获所有异常

java - Spring Boot 中用户定义的休息端点

java - 单击按钮停止线程

java - 为什么 Maven 命令 "mvn sonar:sonar"在我的 "pom.xml"中没有任何插件配置就可以工作?

c++ - C++中的静态初始化和线程安全

multithreading - 在JRuby gem中使用线程安全初始化

c# - 序列化-反序列化(二进制)