首页 > 解决方案 > Local variable can be mutated from child threads in Scala

问题描述

Today I was trying to learn about memory management in the JVM and I came across the following question: can I mutate a local variable from two threads spawned within the same function?

In Java, if you try something like this the code will not compile, yielding an error with message "local variables referenced from an inner class must be final or effectively final"

public class MyClass {
  static void f() throws Exception {
    int x = 0;
    
    Thread t1 = new Thread(new Runnable() {
      public void run() {
        for(int i = 0; i < 1000; i++) {
          x = x + 1;   
        }
      }
    });
     
    Thread t2 = new Thread(new Runnable() {
      public void run() {
        for(int i = 0; i < 1000; i++) {
          x = x - 1;   
        }
      }
    });
      
    t1.start();
    t2.start();
    t1.join();
    t2.join();
    System.out.println(x);
  }
  public static void main(String args[]) throws Exception {
    for(int i = 0; i < 20; i++) {
      f();    
    }
  }
}

However the equivalent code in Scala does compile and run without problem (despite maybe race conditions):

def f(): Unit = {
  var x = 0
  
  val t1 = new Thread(new Runnable {
    override def run(): Unit =
      (1 to 1000).foreach(_ => {x = x + 1})
  })
  
  t1.start()
  val t2 = new Thread(new Runnable {
    override def run(): Unit =
      (1 to 1000).foreach(_ => {x = x - 1})
  })
  t2.start()
  t1.join()
  t2.join()
  println(x)
}

(1 to 20).foreach(_ => f())

Why is the behavior different in each case?

标签: multithreadingscalamemory

解决方案


In Scala lambdas, and by extension anonymous classes, can capture local variables. The scala.runtime package contains some extra classes for that purpose. They effectively lift the local variable into an instance variable of another class whose instances can be shared: https://github.com/scala/scala/blob/v2.13.3/src/library/scala/runtime/ObjectRef.java


推荐阅读