Search code examples
scala

Scala synchornized and volatile decorator not working as expected?


I am trying to understand if synchronised locks the resource appropriately to get me the correct results for following code:

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)
}

But to my surprise, it outputs: 2001 for 90% of the time and 2000 for 10% of the time. Can someone explain to me why this is happening since I am using this. synchronized?

Additionally I tried to use @volatile decorator as well but that seemed to have no effect as well:

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


Solution

  • Your main thread prints the state of newAccount.getAmount right after it started those 2000 threads. There is no determinism regarding how many of those threads executed their work at that point.

    If you want your main thread to print the state of newAccount.getAmount after all 2000 threads mutated it, then you need to wait for all threads to finish, e.g. by joining them:

    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)
    
      val depositThreads = for (_ <- 1 to 1000) yield {
        val t = new Thread(() => newAccount.deposit(1))
        t.start()
        t
      }
    
      val withdrawThreads = for (_ <- 1 to 1000) yield {
        val t = new Thread(() => newAccount.withdraw(1))
        t.start()
        t
      }
    
      withdrawThreads.foreach(_.join)
      depositThreads.foreach(_.join)
    
      println(newAccount.getAmount)
    }