There are many ways to update a value atomically, so without introducing racing conditions in multithreaded
environments.
A well-performing way of doing this in Java is to use an
AtomicReference
or one of
its companion classes in the package
java.util.concurrent.atomic
.
However, using an AtomicReference
is quite cumbersome.
But with Scala, you can easily factor out the cumbersome usage pattern like follows.
For comparison, let’s first see how to atomically update a String
value with an AtomicReference
in Java 7:
AtomicReference<String> ref = new AtomicReference<>("Hello world!");
...
String update;
while (true) {
String expect = ref.get();
update = expect.toUpperCase();
if (ref.compareAndSet(expect, update))
break;
}
upon return from the while
-loop, update
should refer to "HELLO WORLD!"
unless ref
was changed meanwhile.
This is fine if you only have a single reference and a single algorithm to update the referenced value.
However, if you happen to need multiple references or multiple update algorithms for their referenced value, then
repeating the while-loop may be cumbersome and error-prone.
Could we do better?
Sure!
Only the reference and the update algorithm vary from usage to usage, so the repeated pattern is the while
-loop.
We could easily encapsulate this code block in a method, but with Java 7 we would still have to define an interface to
encapsulate the update algorithm and what’s even more important, we would have to write an anonymous inner class every
time we want to use it!
After all, this would save us nothing.
With Scala however, thanks to its closures we can easily do so without hassle.
First, let’s encapsulate the while
-loop in a function:
def atomic[V](ref: AtomicReference[V])(next: V => V): V = {
while (true) {
val expect = ref.get
val update = next(expect)
if (ref.compareAndSet(expect, update))
return update
}
throw new AssertionError
}
This function requires a reference parameter and a function parameter which updates the value of the reference.
Here’s how the first example translates to use the atomic
-function:
val ref = new AtomicReference("Hello world!")
...
atomic(ref)(_.toUpperCase)
Now you can easily apply different update algorithms to the same reference or even vice versa. Here is another example:
atomic(ref)(_.reverse)
Conclusion
Scala’s support for closures allows us to encapsulate repeated code blocks while still being able to easily call the
encapsulated code blocks.
This particular example showed us how to encapsulate the cumbersome while
-loop which is required to atomically update
an AtomicReference
.