How and when should we use `AtomicReference` in Java?

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 an AtomicInteger is probably more appropriate. AtomicReference is really useful when you’re dealing with objects.