反射

執行緒安全性

語言
此文件頁面是針對 Scala 2 中發布的功能,這些功能已在 Scala 3 中移除或由其他功能取代。除非另有說明,此頁面中的所有程式碼範例都假設您使用的是 Scala 2。

實驗性質

Eugene Burmako

很遺憾,在 Scala 2.10.0 中發布的現況下,反射並非執行緒安全的。有一個 JIRA 問題 SI-6240 可用於追蹤我們的進度並查看技術細節,以下是技術現況的簡要摘要。

NEW 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 公開)較不容易受到執行緒問題的影響。第一個原因是,當巨集有機會執行時,編譯時期反射宇宙已初始化,這排除了競爭狀況 #1。第二個原因是,編譯器從未執行緒安全,因此沒有工具預期會並行執行。不過,如果您的巨集產生多個執行緒,您仍應小心。

不過,對於執行時期反射來說,情況就糟多了。反射初始化會在第一次初始化 scala.reflect.runtime.universe 時被呼叫,而這個初始化可能會以間接的方式發生。最明顯的例子就是呼叫具有 TypeTag 內容邊界的函式可能會造成問題,因為為了呼叫此類函式,Scala 通常需要建構自動產生的類型標籤,而這需要建立類型,進而需要初始化反射宇宙。推論就是,如果你沒有採取特殊措施,你就無法在測試中可靠地使用基於 TypeTag 的函式,因為許多工具(例如 sbt)會並行執行測試。

重點

  • 如果你編寫的巨集沒有明確建立執行緒,那就沒問題。
  • 執行時期反射與執行緒或執行者混合使用可能會很危險。
  • 多個執行緒呼叫具有 TypeTag 內容邊界的函式可能會導致非確定性結果。
  • 查看 SI-6240,了解我們在處理這個問題上的進度。

此頁面的貢獻者