1
\$\begingroup\$

Update: Now that https://github.com/bionicspirit/scala-atomic is available, I have less of a need for this.

Am I doing anything bad? I wrote this class to allow modifying an atomic reference with the assurance that two threads modifying it will not step on each other's toes. To illustrate:

val x = new AtomicRef(List.empty[String])
def compute(s: String) = { Thread.sleep(1000); s }
def updateX(s: String) = x.transform(strings => compute(s) :: strings)

The idea is to guarantee that if two threads call updateX many times, all the strings passed to updateX will wind up in x.

run is like transform but it lets you return a value to the caller other than the value you're setting into the reference.

Here is the class:

/**
 * A subclass of java.util.concurrent.atomic.AtomicReference[A]
 * that adds the ability to atomically modify its value relative to itself.
 */
class AtomicRef[A](init: A) extends java.util.concurrent.atomic.AtomicReference[A](init) {
  /**
   * Update the value atomically by applying a function to it.
   * Uses `compareAndSet` and will keep trying until it succeeds.
   */
 final def transform(f: A => A): A = run { a =>
    val ret = f(a)
    (ret, ret)
 }
  /**
   * Update the value atomically by applying a computation to it.
   * The computation returns a pair of values, of which the
   * `AtomicRef` is updated to the first.
   * Uses `compareAndSet` and will keep trying until it succeeds.
   * @return the second value of the computation
   */
  @annotation.tailrec final def run[B](f: A => (A, B)): B = {
    val a = get
    val (a2, b) = f(a)
    if(compareAndSet(a, a2)) b
    else {
      Thread.sleep(10)
      run(f)
    }
  }
}

To put it in different terms, this sort of abstracts over doing something like this (in Java; List here is a fictional immutable list):

AtomicReference<List<String>> ref = new AtomicReference<List<String>>(new List());
void update(String s) {
  while(true) {
    List<String> a = ref.get;
    // now do "f(a)"
    Thread.sleep(1000); // pretend it takes a long time to run the computation
    List<String> b = a.prepend(s);
    if(compareAndSet(a, b)) return b;
    else {
      Thread.sleep(10);
      // loop the while instead of tail recursion
    }
  }
}
\$\endgroup\$
  • \$\begingroup\$ This cannot be really called atomic, especially the run function; if another thread has a reference to this AtomicReference it can modify the value from under your nose. What is the intent of this class exactly? \$\endgroup\$ – fge Jun 17 '13 at 7:55
  • \$\begingroup\$ Hi @fge, apologies for not describing the intent before. I added it to the question text. Is it clear? \$\endgroup\$ – nafg Jun 18 '13 at 8:59
  • \$\begingroup\$ @fge It's true another thread can modify the value while the computation is running. But then compareAndSet will fail (return false) and we will try again! compareAndSet means don't store the result of the computation unless the value still is what the computation thought it was! \$\endgroup\$ – nafg Jun 18 '13 at 9:01
  • \$\begingroup\$ Uh, OK, I don't understand scala well enough to really comment :/ \$\endgroup\$ – fge Jun 18 '13 at 9:02
  • \$\begingroup\$ @fge thanks anyway, I added a Java snippet that illustrates the pattern, except now having functions it doesn't abstract over the operation. \$\endgroup\$ – nafg Jun 19 '13 at 17:08
2
\$\begingroup\$

Your approach is definitely valid, I've seen something similar used in GHC's implementation of atomicModifyIORef.

However I have some doubts if such a class is really useful. If the computations are potentially long running, the performance advantage of using AtomicReference instead of some standard synchronization primitive is negligible. Moreover, if there are several updating threads, it will result in repeated recomputation, wasting CPU power on it. So using plain synchronized on some private lock object (so that no other part of code can lock on it) seems simpler, safer and with better performance.

\$\endgroup\$
  • \$\begingroup\$ Okay, that's a good point. I would say that this class is designed for quick computations, but designed in a reusable way. So the decision whether to use this class would need to be made in each use site. \$\endgroup\$ – nafg Jul 25 '13 at 2:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.