1

I have the following code that set the Atomic variable (both java.util.concurrent.atomic and monix.execution.atomic behaves the same:

class Foo {
  val s = AtomicAny(null: String)

  def foo() = {
    println("called")
    /* Side Effects */ 
    "foo" 
  }

  def get(): String = {
    s.compareAndSet(null, foo())
    s.get
  }
}


val f = new Foo
f.get //Foo.s set from null to foo, print called
f.get //Foo.s not updated, but still print called

The second time it compareAndSet, it did not update the value, but still foo is called. This is causing problem because foo is having side effects (in my real code, it creates an Akka actor and give me error because it tries to create duplicate actors).

How can I make sure the second parameter is not evaluated unless it is actually used? (Preferably not using synchronized)

I need to pass implicit parameter to foo so lazy val would not work. E.g.

  lazy val s = get() //Error cannot provide implicit parameter

  def foo()(implicit context: Context) = {
    println("called")
    /* Side Effects */ 
    "foo" 
  }

  def get()(implicit context: Context): String = {
    s.compareAndSet(null, foo())
    s.get
  }
SwiftMango
  • 15,092
  • 13
  • 71
  • 136
  • How do you want it to behave when it had been `null` at first but it was no longer `null` after calling `foo`? Don't you need a lock if you don't want this happen? – snak Jul 03 '19 at 23:55
  • @snak I am not sure I am following... I think there might be a misunderstanding of my question. See I just updated – SwiftMango Jul 03 '19 at 23:58
  • Well, atomic variables support comparing and swapping values at the same time without using locks (probably using a single CPU instruction). But to do that, it needs to know the values to be swapped before calling `compareAndSet`. That's why its arguments cannot be lazy, and you need an explicit lock to lock it while calling your own function. – snak Jul 04 '19 at 01:03

1 Answers1

1

Updated answer

The quick answer is to put this code inside an actor and then you don't have to worry about synchronisation.

If you are using Akka Actors you should never need to do your own thread synchronisation using low-level primitives. The whole point of the actor model is to limit the interaction between threads to just passing asynchronous messages. This provides all the thread synchronisation that you need and guarantees that an actor processes a single message at a time in a single-threaded manner.

You should definitely not have a function that is accessed simultaneously by multiple threads that creates a singleton actor. Just create the actor when you have the information you need and pass the ActorRef to any other actors that need it using dependency injection or a message. Or create the actor at the start and initialise it when the first message arrives (using context.become to manage the actor state).


Original answer

The simplest solution is just to use a lazy val to hold your instance of foo:

class Foo {
  lazy val foo = {
    println("called")
   /* Side Effects */ 
   "foo" 
  }
}

This will create foo the first time it is used and after that will just return the same value.

If this is not possible for some reason, use an AtomicInteger initialised to 0 and then call incrementAndGet. If this returns 1 then it is the first pass through this code and you can call foo.

Explanation:

Atomic operations such as compareAndSet require support from the CPU instruction set, and modern processors have single atomic instructions for such operations. In some cases (e.g. cache line is held exclusively by this processor) the operation can be very fast. Other cases (e.g. cache line also in cache of another processor) the operation can be significantly slower and can impact other threads.

The result is that the CPU must be holding the new value before the atomic instruction is executed. So the value must be computed before it is known whether it is needed or not.

Community
  • 1
  • 1
Tim
  • 26,753
  • 2
  • 16
  • 29
  • `lazy val` does not work in my case because the side effects shouldn't take place at the time of class initialization, and I need to pass implicit parameter to foo which lazy val cannot do. – SwiftMango Jul 04 '19 at 15:28
  • @texasbruce The side effects do not take place at class initialisation, they take place at first access to `foo`. You can assign `foo` from a function that takes `implicit` parameters. – Tim Jul 04 '19 at 15:40
  • The implicit parameter is provided by the function that calls `get` and cannot be provided at the time lazy val is created. See my updated questions. – SwiftMango Jul 04 '19 at 15:42