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
}
}
}
1 Answer 1
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.
-
\$\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– nafg2013年07月25日 02:51:17 +00:00Commented Jul 25, 2013 at 2:51
run
function; if another thread has a reference to thisAtomicReference
it can modify the value from under your nose. What is the intent of this class exactly? \$\endgroup\$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\$