Search code examples
multithreadingscalaimplicitmutability

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?


Solution

  • Spoiler alert: It's not complicated. It's funny, like a magic trick (if you consider reading the documentation about Java Memory Model "funny", that is). If you haven't figured it out yet, I would highly recommend to try to figure it out, otherwise it won't be funny. Someone should make a "division-by-zero proves 2 = 4"-riddle out of it.


    Consider the following shorter example:

    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)
    

    When run, it prints

    not initialized
    

    despite the fact that we invoked computeResult() and set result to "Yay, result!". The problem is that the two invocations a.computeResult() and a.result belong to two completely independent instances of Foo. The implicit conversion is performed twice, and the second implicitly created object doesn't know anything about the changes in the first implicitly created object. It has nothing to do with threads or JMM at all.

    By the way: your code is not parallel. Calling join right after calling start doesn't bring you anything, your main thread will simply go idle and wait until another thread finishes. At no point will there be two threads that do any useful work concurrently.