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
 }
 }
}
asked Jun 17, 2013 at 6:33
\$\endgroup\$
5
  • \$\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\$ Commented Jun 17, 2013 at 7:55
  • \$\begingroup\$ Hi @fge, apologies for not describing the intent before. I added it to the question text. Is it clear? \$\endgroup\$ Commented Jun 18, 2013 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\$ Commented Jun 18, 2013 at 9:01
  • \$\begingroup\$ Uh, OK, I don't understand scala well enough to really comment :/ \$\endgroup\$ Commented Jun 18, 2013 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\$ Commented Jun 19, 2013 at 17:08

1 Answer 1

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.

answered Jul 23, 2013 at 13:01
\$\endgroup\$
1
  • \$\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\$ Commented Jul 25, 2013 at 2:51

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.