Exactly, Tim! The example you showed is perfect for configurations, but java AtomicReference can be just as useful when you’re managing complex custom objects. For instance, let’s say you’re building a concurrent counter that tracks some custom state. Unlike primitive types, these custom objects need atomic updates as well. You can use AtomicReference for that too, just like this:
import java.util.concurrent.atomic.AtomicReference;
class Counter {
private final int count;
public Counter(int count) {
this.count = count;
}
public Counter increment() {
return new Counter(count + 1);
}
public int getCount() {
return count;
}
}
public class AtomicReferenceCounter {
public static void main(String[] args) {
AtomicReference<Counter> counterRef = new AtomicReference<>(new Counter(0));
Runnable task = () -> {
for (int i = 0; i < 5; i++) {
Counter prev, next;
do {
prev = counterRef.get();
next = prev.increment();
} while (!counterRef.compareAndSet(prev, next));
System.out.println(Thread.currentThread().getName() + " -> Counter: " + next.getCount());
}
};
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
t1.start();
t2.start();
}
}
Why use this?
- It’s a great way to implement atomic updates for custom objects like a counter.
- Provides a lock-free alternative to synchronized methods.
- Useful when multiple threads need to safely modify shared objects.
When NOT to use this?
- If you’re just updating a simple primitive type like an
int, then anAtomicIntegeris probably more appropriate.AtomicReferenceis really useful when you’re dealing with objects.