首页 > 解决方案 > Implicit class holding mutable variable in multithreaded environment

问题描述

I need to implement a parallel method, which takes two computation blocks, a and b, and starts each of them in a new thread. The method must return a tuple with the result values of both the computations. It should have the following signature:

def parallel[A, B](a: => A, b: => B): (A, B)

I managed to solve the exercise by using straight Java-like approach. Then I decided to make up a solution with implicit class. Here's it:

object ParallelApp extends App {

  implicit class ParallelOps[A](a: => A) {
    var result: A = _

    def spawn(): Unit = {

      val thread = new Thread {
        override def run(): Unit = {
          result = a
        }
      }
      thread.start()
      thread.join()
    }
  }

  def parallel[A, B](a: => A, b: => B): (A, B) = {
    a.spawn()
    b.spawn()
    (a.result, b.result)

  }

  println(parallel(1 + 2, "a" + "b"))

}

For unknown reason, I receive output (null,null). Could you please point me out where is the problem?

标签: multithreadingscalaimplicitmutability

解决方案


剧透警报:这并不复杂。这很有趣,就像魔术一样(如果您考虑阅读有关 Java 内存模型“有趣”的文档,那就是)。如果您还没有弄清楚,我强烈建议您尝试弄清楚,否则不会很有趣。有人应该从中得出一个“除以零证明 2 = 4”的谜题。


考虑以下较短的示例:

implicit class Foo[A](a: A) {
  var result: String = "not initialized"
  def computeResult(): Unit = result = "Yay, result!"
}

val a = "a string"
a.computeResult()

println(a.result)

运行时,它会打印

not initialized

尽管我们调用computeResult()并设置result"Yay, result!". 问题是这两个调用属于两个完全独立的a.computeResult()实例。隐式转换执行了两次,第二个隐式创建的对象对第一个隐式创建的对象的更改一无所知。它与线程或 JMM 完全无关。a.resultFoo

顺便说一句:您的代码不是并行的。调用join后立即调用start不会给您带来任何好处,您的主线程只会闲置并等待另一个线程完成。绝不会有两个线程同时做任何有用的工作。


推荐阅读