Understanding and Using AtomicReference for Thread‑Safe Updates in Java
This article explains the limitations of volatile for compound updates, demonstrates non‑thread‑safe bank‑account examples, introduces synchronized locking, and then shows how AtomicReference with CAS operations provides a lock‑free, thread‑safe solution, including a deep dive into its internal implementation and related memory‑barrier concepts.
Previously we introduced atomic utilities such as AtomicInteger , AtomicLong and AtomicBoolean ; now we continue exploring the classes under the java.util.concurrent.atomic package, focusing on AtomicReference .
Basic Usage of AtomicReference
We start with a simple BankCard class that represents a personal bank account with immutable fields accountName and money :
public class BankCard {
private final String accountName;
private final int money;
// constructor
public BankCard(String accountName, int money) {
this.accountName = accountName;
this.money = money;
}
public String getAccountName() { return accountName; }
public int getMoney() { return money; }
@Override
public String toString() {
return "BankCard{" + "accountName='" + accountName + '\'' + ", money='" + money + '\'' + '}';
}
}Assuming multiple threads deposit money into the same account, we first test a naïve implementation using a volatile reference:
public class BankCardTest {
private static volatile BankCard bankCard = new BankCard("cxuan", 100);
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
final BankCard card = bankCard; // read reference
BankCard newCard = new BankCard(card.getAccountName(), card.getMoney() + 100);
System.out.println(newCard);
bankCard = newCard; // write back
try { TimeUnit.MICROSECONDS.sleep(1000); } catch (Exception e) { e.printStackTrace(); }
}).start();
}
}
}The output shows that the final amount is only 900 instead of the expected 1100, because the read‑modify‑write sequence is not atomic even though each individual operation sees the latest value.
Where does the problem occur?
Although volatile guarantees visibility, the combination of "get reference" and "modify reference" is a compound action that is not atomic, leading to lost updates when threads interleave.
The simplest fix is to protect the critical section with synchronized :
public class BankCardSyncTest {
private static volatile BankCard bankCard = new BankCard("cxuan", 100);
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
synchronized (BankCardSyncTest.class) {
final BankCard card = bankCard;
BankCard newCard = new BankCard(card.getAccountName(), card.getMoney() + 100);
System.out.println(newCard);
bankCard = newCard;
try { TimeUnit.MICROSECONDS.sleep(1000); } catch (Exception e) { e.printStackTrace(); }
}
}).start();
}
}
}Running this version yields the correct final amount because the lock serialises the updates.
Beyond synchronized , the java.util.concurrent.atomic package offers AtomicReference , which provides lock‑free thread safety.
Using AtomicReference
public class BankCardARTest {
private static AtomicReference
bankCardRef =
new AtomicReference<>(new BankCard("cxuan", 100));
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
while (true) {
final BankCard card = bankCardRef.get();
BankCard newCard = new BankCard(card.getAccountName(), card.getMoney() + 100);
if (bankCardRef.compareAndSet(card, newCard)) {
System.out.println(newCard);
}
try { TimeUnit.SECONDS.sleep(1); } catch (Exception e) { e.printStackTrace(); }
}
}).start();
}
}
}Here get() obtains the current reference atomically, and compareAndSet (CAS) attempts a non‑blocking update only if the reference has not changed in the meantime. The output shows correctly increasing balances without duplicate values, though the order may appear interleaved due to thread scheduling.
AtomicReference Internals
AtomicReference shares its implementation pattern with AtomicInteger . Internally it holds three fields:
Unsafe – a class in sun.misc that provides low‑level native operations.
valueOffset – the memory offset of the value field, obtained via Unsafe.objectFieldOffset .
value – the actual stored reference, declared volatile for visibility.
The CAS operation is performed by Unsafe.compareAndSwapObject , which ultimately invokes the native Atomic:cmpxchg instruction. The JDK source for this resides in hotspot/src/share/vm/prims/unsafe.cpp .
When the JVM runs in 64‑bit mode, pointer compression ( UseCompressedOops ) may be enabled to store object references in 32‑bit form, reducing heap usage and improving cache locality. If disabled, references occupy 8 bytes, increasing GC pressure and lowering CPU cache hit rates.
Key Methods
get() – atomically reads the current reference.
set() – atomically writes a new reference.
lazySet() – a weaker write that may delay the visibility of the update, useful for performance‑critical paths.
getAndSet() – atomically replaces the value and returns the previous one, implemented via a loop that uses getObjectVolatile and compareAndSwapObject .
compareAndSet() – the core CAS operation that succeeds only when the expected reference matches the current one.
weakCompareAndSet() – a variant that may fail spuriously but can be faster on some architectures; it still ultimately maps to the same native CAS primitive.
All these methods rely on the volatile semantics of the underlying value field, ensuring that reads and writes are performed directly on main memory.
Summary
The article introduced the motivation behind AtomicReference, demonstrated its usage through a bank‑account example, compared it with volatile and synchronized , and dissected the underlying source code and JVM mechanisms such as Unsafe , CAS, and pointer compression. It provides a comprehensive view of how to achieve lock‑free thread safety for object references in Java.
Full-Stack Internet Architecture
Introducing full-stack Internet architecture technologies centered on Java
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.