反射

线程安全性

语言
此文档页面特定于 Scala 2 中提供的功能,这些功能已在 Scala 3 中删除或被替代功能取代。除非另有说明,此页面中的所有代码示例均假定你使用的是 Scala 2。

实验性

Eugene Burmako

遗憾的是,在 Scala 2.10.0 中发布的当前状态下,反射不是线程安全的。有一个 JIRA 问题 SI-6240,可用于跟踪我们的进度并查找技术详细信息,以下是最新状态的简要摘要。

 Scala 2.11.0-RC1 中已修复线程安全问题,但我们现在仍将保留此文档,因为 Scala 2.10.x 系列中仍然存在此问题,而且我们目前还没有关于何时会移植修复程序的具体计划。

目前,我们了解到与反射相关的两种竞争。首先,反射初始化(当首次访问 scala.reflect.runtime.universe 时调用的代码)不能从多个线程安全调用。其次,符号初始化(首次访问符号的标志或类型签名时调用的代码)也不安全。以下是典型的表现形式

java.lang.NullPointerException:
at s.r.i.Types$TypeRef.computeHashCode(Types.scala:2332)
at s.r.i.Types$UniqueType.<init>(Types.scala:1274)
at s.r.i.Types$TypeRef.<init>(Types.scala:2315)
at s.r.i.Types$NoArgsTypeRef.<init>(Types.scala:2107)
at s.r.i.Types$ModuleTypeRef.<init>(Types.scala:2078)
at s.r.i.Types$PackageTypeRef.<init>(Types.scala:2095)
at s.r.i.Types$TypeRef$.apply(Types.scala:2516)
at s.r.i.Types$class.typeRef(Types.scala:3577)
at s.r.i.SymbolTable.typeRef(SymbolTable.scala:13)
at s.r.i.Symbols$TypeSymbol.newTypeRef(Symbols.scala:2754)

好消息是,编译时反射(通过 scala.reflect.macros.Context 向宏公开的反射)比运行时反射(通过 scala.reflect.runtime.universe 公开的反射)更容易受到线程问题的影响。第一个原因是,当宏有机会运行时,编译时反射 universe 已初始化,这排除了竞争条件 1。第二个原因是编译器从未实现线程安全,因此没有工具期望并行运行。不过,如果你的宏生成多个线程,你仍应小心。

但对于运行时反射来说,情况要糟糕得多。当 scala.reflect.runtime.universe 初始化时,将首次调用反射初始化,并且此初始化可能以间接方式发生。这里最突出的示例是调用具有 TypeTag 上下文边界的函数可能存在问题,因为为了调用此类函数,Scala 通常需要构造一个自动生成的类型标记,该标记需要创建一个类型,该类型需要初始化反射 universe。推论是,如果你不采取特殊措施,你无法在测试中可靠地使用基于 TypeTag 的函数,因为许多工具(例如 sbt)并行运行测试。

底线

  • 如果您正在编写一个不显式创建线程的宏,那么您完全没问题。
  • 将运行时反射与线程或 actor 混合使用可能是危险的。
  • 使用 TypeTag 上下文边界的多个线程调用方法可能会导致非确定性结果。
  • 查看 SI-6240 以了解我们在这个问题上的进展。

此页面的贡献者