Scala 3中含for循环的线程无法调用join的异常问题
Scala 3中
object extends App结合线程、join与集合遍历的异常问题 问题现象
在Scala 3里,当你写了一个继承App的object,里面启动新线程,线程的run方法用for循环或者集合foreach,同时主线程调用t.join()时,会出现诡异异常:线程能打印Started,但循环及之后的代码完全不执行。只要注释掉for循环或者join()任意一个,代码就正常运行。
更具体的细节:
- 把for循环换成
while遍历迭代器,代码能正常打印; - 把循环逻辑放到单独的类里、用REPL运行、用
@main注解的脚本、或者在Scala 2里跑同样的代码,都没问题; - 如果把
join()换成Thread.sleep(3000),新线程的循环要等主线程休眠结束才会开始执行。
问题代码示例:
object Main extends App { val t = new Thread() { override def run() = { println("Started") for (j <- 1 to 2) {println("Work")} } } t.start() t.join() }
补充测试:
- 换成集合
foreach同样失效:
val it = List(1, 2, 3) it.foreach(x => println(x))
- 但用
while遍历迭代器却正常:
val it = List(1, 2, 3).iterator while(it.hasNext) println(it.next())
问题根源
这是Scala 3对App特质的实现机制导致的:
Scala 3的App用了延迟初始化的改进逻辑,继承App的object里的代码会被包装到一个特殊的初始化方法中,而不是直接在主线程的main方法里执行。当主线程调用t.join()阻塞时,App的初始化流程还没走完,集合类(比如Range、List)的加载/初始化会被主线程的阻塞卡住——新线程要用到这些集合类,但它们的初始化依赖主线程完成某些步骤,于是就出现了类似死锁的阻塞,导致循环代码无法执行。
而while循环直接操作迭代器,不需要触发那些被阻塞的集合类初始化逻辑;换成@main、单独类或者Scala 2的App,代码是直接在标准的main方法里执行,不存在这个初始化阻塞的问题。
解决办法
有三种简单的修复方式:
1. 改用@main注解替代extends App
这是Scala 3推荐的启动方式,完全避开App的初始化问题:
@main def main(): Unit = { val t = new Thread() { override def run() = { println("Started") for (j <- 1 to 2) {println("Work")} } } t.start() t.join() }
2. 将线程逻辑放到独立类中
把线程的run方法逻辑移到单独的类里,避免和App的初始化逻辑纠缠:
object Main extends App { class WorkerThread extends Thread { override def run() = { println("Started") for (j <- 1 to 2) {println("Work")} } } val t = new WorkerThread() t.start() t.join() }
3. 手动实现main方法
直接写标准的main方法,绕过App特质的逻辑:
object Main { def main(args: Array[String]): Unit = { val t = new Thread() { override def run() = { println("Started") for (j <- 1 to 2) {println("Work")} } } t.start() t.join() } }
内容的提问来源于stack exchange,提问作者Lox
相关产品推荐
相关产品推荐

