You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Scala中synchronized与volatile未按预期工作?求原因解析

问题描述

我尝试验证synchronized是否能正确锁定资源,写出了如下Scala代码:

object ThreadsExample extends App {
  private class BankAccount(private var balance:Int = 0) {
    def getAmount:Int = this.balance
    def deposit(amount:Int):Unit = this.synchronized {this.balance += amount}
    def withdraw(amount:Int):Unit = this.synchronized{this.balance -= amount}
  }

  private val newAccount: BankAccount = new BankAccount(2000)

  for(_ <- 1 to 1000) {
    new Thread(() => newAccount.deposit(1)).start()
  }

  for (_ <- 1 to 1000) {
    new Thread(() => newAccount.withdraw(1)).start()
  }

  println(newAccount.getAmount)
}

但实际运行结果出乎意料:90%的情况输出2001,10%的情况输出2000。明明已经用了this.synchronized,为什么会出现这种情况?

另外我还尝试给balance加上@volatile注解:

private class BankAccount(@volatile private var balance:Int = 0)

但依旧没有效果。


问题原因与解决方案
  • 根本原因:主线程未等待子线程执行完毕
    你代码里的主线程(App对应的线程)在启动2000个子线程后,没有做任何等待就直接执行println输出余额。此时大部分子线程可能还没完成deposit或withdraw操作,导致读取到的是中间状态的余额。比如主线程先执行输出时,可能还有几个deposit线程已执行完,但对应的withdraw线程还没运行,最终导致余额偏离预期的2000。

  • 为什么synchronized和volatile没用?

    • synchronized已经正确保证了deposit和withdraw操作的原子性,不会出现多线程同时修改balance导致的数值错乱,但它管不了主线程和子线程的执行顺序。
    • @volatile主要保证变量的可见性,让线程能立刻读取到变量的最新值,但同样解决不了主线程提前执行输出的问题。
  • 修复方案:等待所有子线程执行完成
    把所有创建的子线程保存到集合中,调用join()方法等待它们全部执行完毕后,再输出余额。修改后的代码示例:

object ThreadsExample extends App {
  private class BankAccount(private var balance:Int = 0) {
    def getAmount:Int = this.synchronized { this.balance } // 给get加锁,保证读取最新值
    def deposit(amount:Int):Unit = this.synchronized {this.balance += amount}
    def withdraw(amount:Int):Unit = this.synchronized{this.balance -= amount}
  }

  private val newAccount: BankAccount = new BankAccount(2000)

  // 保存所有存款、取款线程
  val depositThreads = (1 to 1000).map(_ => new Thread(() => newAccount.deposit(1)))
  val withdrawThreads = (1 to 1000).map(_ => new Thread(() => newAccount.withdraw(1)))

  // 启动所有线程
  depositThreads.foreach(_.start())
  withdrawThreads.foreach(_.start())

  // 等待所有线程执行完毕
  depositThreads.foreach(_.join())
  withdrawThreads.foreach(_.join())

  println(newAccount.getAmount)
}

额外建议给getAmount方法也加上synchronized,确保读取余额时能获取到同步后的最新值,避免可见性问题导致的旧值读取。

内容的提问来源于stack exchange,提问作者Shivam Sahil

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 01:00:11