Scala对象初始化时线程冻结问题:为何self引用可正常执行?
解析Scala单例对象初始化与线程死锁的问题
你遇到的这个线程冻结问题,本质是Scala单例对象的线程安全初始化机制和代码里的线程等待逻辑形成了死锁,咱们一步步把这个逻辑理清楚:
先回顾你的复现代码
object Alpha { var r = "A" r = "B" System.out.println("Not threaded: r = " + r) val thread = { val self = this new Thread( new Runnable { def run() = { System.out.println(" Started.") System.out.println(" Threaded self.r = " + self.r) self.r = "C" System.out.println(" Threaded self.r = " + self.r) // At the following line the thread freezes! System.out.println(" Threaded r = " + r) r = "D" System.out.println(" Threaded r = " + r) } }) } thread.start thread.join }
1. Scala单例对象的初始化规则
Scala的object是JVM层面的延迟初始化单例,JVM为了保证它的线程安全,做了以下核心设计:
- 当第一个线程尝试访问单例的成员时,会触发类的初始化流程,此时JVM会给这个类加一个初始化锁(class initialization lock)。
- 在初始化完成之前,任何其他试图访问该类成员的线程都会被阻塞,直到锁被释放。
2. 你的代码里的死锁是怎么形成的?
我们跟着代码的执行顺序走一遍:
- 主线程开始初始化
Alpha对象,执行到thread.start时,成功启动了新线程。 - 主线程紧接着执行
thread.join,开始等待新线程执行完毕——注意,此时主线程还在Alpha的初始化过程中,手里还攥着Alpha$类的初始化锁。 - 新线程跑到
System.out.println(" Threaded r = " + r)这一行时,直接写r相当于访问Alpha.r,这会触发JVM对Alpha$类的初始化检查。但此时主线程还拿着初始化锁,新线程只能被阻塞,等着锁释放。 - 这下就陷入了死循环:主线程等新线程结束,新线程等主线程放锁,双方互相卡住,形成死锁,所以线程看起来就“冻结”了。
3. 为什么用self.r就一切正常?
你在创建线程时捕获了val self = this,这个self指向的是正在构造中的Alpha实例——虽然它还没完成初始化,但已经是一个有效的对象引用了。
当你通过self.r访问变量时,是直接访问这个已存在的实例的字段,不会触发JVM的类初始化检查(毕竟我们已经在初始化过程中了,没必要再去抢那个锁),所以新线程可以顺畅执行,不会被阻塞,自然也就不会死锁。
额外验证:打破死锁的小实验
如果你把thread.join移到Alpha初始化代码的外面,比如写一个独立的main方法:
object Main { def main(args: Array[String]): Unit = { Alpha.thread.join } }
这时候直接引用r也不会冻结——因为Alpha的初始化已经在主线程调用Alpha.thread时完成了,主线程早就释放了初始化锁,新线程访问r不会被卡住。
内容的提问来源于stack exchange,提问作者Datakneder
相关产品推荐
相关产品推荐

