Scala构造器中final字段相关Happens-before规则实现方案问询
Java规范规定,仅包含final字段的类,其构造器执行与任意读取该对象引用的线程之间存在happens-before关系,即应用程序不可能观测到处于部分构造状态的对象。
Scala为了保证「主构造器vals」在父类所有初始化代码执行前完成赋值,会将初始化逻辑提取到独立方法做特殊处理,这也是Scalafinal val并不总会(甚至永远不会)被编译为Javafinal字段的原因之一。
需要实现符合以下要求的方案:
- 可实现类构造器与类调用方之间的happens-before关系
- 对应方案依托的是Scala编译器相对稳定的特性
- 无需采用Java编写该类即可实现需求
方案1:使用lazy val持有实例/定义字段
Scala规范明确规定了lazy val的初始化线程安全语义:lazy val的初始化操作与后续所有对该值的读取操作之间存在happens-before关系,不会出现半初始化状态的对象对外暴露的问题。
该特性从Scala 2.x版本起就稳定存在,属于Scala语言标准定义的核心语义,不存在跨版本兼容风险。
使用示例:
// 所有字段直接定义为lazy val class SafeImmutableClass { lazy val field1: String = "init value" lazy val field2: Int = 100 } // 或者用lazy val统一持有实例 object SafeInstanceHolder { lazy val safeInstance: SafeImmutableClass = new SafeImmutableClass() }
方案2:为所有实例字段添加@volatile注解
依托JMM的volatile变量规则:对volatile字段的写入操作happens-before后续所有对该字段的读取操作。只要保证对象在构造完成后再对外发布,所有调用方线程读取到的字段值都是构造完成后的最终值,不会观测到部分构造的状态。
@volatile是Scala长期支持的注解,直接映射到Java的volatile字段修饰符,语义稳定。
使用示例:
class SafeImmutableClass { @volatile val field1: String = "init value" @volatile val field2: Int = 100 }
方案3:使用单例object定义无状态工具类/全局唯一实例
Scala的object单例的初始化过程由编译器保证线程安全,类构造完成和后续读取操作之间天然存在happens-before关系,完全不会出现半构造状态暴露的问题。如果你的场景适合用单例实现,该方案是成本最低的选择。
使用示例:
object SafeSingleton { val field1: String = "init value" val field2: Int = 100 def doSomething(): Unit = {} }
内容的提问来源于stack exchange,提问作者Turin

