Java VarHandle: Atomic Field Access and Memory Ordering
VarHandle, introduced in Java 9 (JEP 193), provides a type-safe, standardized API for atomic operations on fields, array elements, and off-heap memory, replacing the infamous sun.misc.Unsafe. It also exposes all four Java Memory Model access modes (plain, opaque, acquire/release, volatile), giving you fine-grained control over memory ordering without always paying the full cost of volatile.
Contents
Obtaining a VarHandle
A VarHandleMethodHandles.lookup()findVarHandle()static final
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
class Counter {
private volatile int value;
// Declare VarHandle as static final in the same class
private static final VarHandle VALUE;
static {
try {
VALUE = MethodHandles.lookup()
.findVarHandle(Counter.class, "value", int.class);
} catch (ReflectiveOperationException e) {
throw new ExceptionInInitializerError(e);
}
}
public int get() { return (int) VALUE.get(this); }
public void set(int v) { VALUE.set(this, v); }
public int getAndIncrement() {
return (int) VALUE.getAndAdd(this, 1); // returns old value
}
public boolean compareAndSet(int expected, int newValue) {
return VALUE.compareAndSet(this, expected, newValue);
}
}
VarHandleMethodHandles.lookup()MethodHandles.privateLookupIn()Atomic Operations
VarHandlecompareAndSet()getAndAdd()getAndSet()AtomicInteger
class Node<T> {
T item;
Node<T> next;
private static final VarHandle NEXT;
static {
try {
NEXT = MethodHandles.lookup()
.findVarHandle(Node.class, "next", Node.class);
} catch (ReflectiveOperationException e) {
throw new ExceptionInInitializerError(e);
}
}
// compareAndSet — the core of lock-free algorithms
boolean casNext(Node<T> expected, Node<T> replacement) {
return NEXT.compareAndSet(this, expected, replacement);
}
// getAndSet — atomically swaps value
Node<T> getAndSetNext(Node<T> newNext) {
return (Node<T>) NEXT.getAndSet(this, newNext);
}
}
// Available atomic operations on VarHandle
// compareAndSet(Object obj, Object expected, Object newValue) → boolean
// compareAndExchange(obj, expected, newValue) → Object (returns witness value)
// weakCompareAndSetPlain(obj, expected, newValue) → boolean (may fail spuriously)
// getAndSet(obj, newValue) → Object (old value)
// getAndAdd(obj, delta) → number (old value, only for numeric types)
// getAndBitwiseOr / getAndBitwiseAnd / getAndBitwiseXor
Memory Ordering Modes
VarHandle exposes all four Java Memory Model access modes. Choose the weakest ordering that still meets your correctness requirements:
// plain — no ordering guarantees (like non-volatile field access)
VALUE.set(obj, 42); // plain set
int v = (int) VALUE.get(obj); // plain get
// opaque — coherent (coherent per-variable ordering), but no cross-variable ordering
VALUE.setOpaque(obj, 42);
int vo = (int) VALUE.getOpaque(obj);
// release / acquire — establishes happens-before like volatile read/write
// (but only for pairs — the release write is visible to the acquire read)
VALUE.setRelease(obj, 42); // "publish" — like a release fence
int va = (int) VALUE.getAcquire(obj); // "consume" — like an acquire fence
// volatile — full sequential consistency (most expensive, but always safe)
VALUE.setVolatile(obj, 42);
int vv = (int) VALUE.getVolatile(obj); // equivalent to reading a volatile field
// Example: lightweight publish/subscribe flag
class SharedState {
private boolean ready = false;
private int data = 0;
private static final VarHandle READY;
// ... (static initializer as above)
void publish(int value) {
data = value; // plain write
READY.setRelease(this, true); // release fence — data visible after this
}
int consume() {
if ((boolean) READY.getAcquire(this)) { // acquire fence — sees all writes before setRelease
return data;
}
return -1;
}
}
plainopaquevolatileAtomicIntegerArray and ByteBuffer VarHandles
MethodHandles.arrayElementVarHandle()VarHandlecompareAndSetgetAndAdd
// Array VarHandle — atomic access to array elements
VarHandle arrayHandle = MethodHandles.arrayElementVarHandle(int[].class);
int[] arr = new int[10];
arrayHandle.set(arr, 3, 42); // arr[3] = 42
int val = (int) arrayHandle.get(arr, 3); // val = arr[3]
arrayHandle.compareAndSet(arr, 3, 42, 99); // CAS on arr[3]
int old = (int) arrayHandle.getAndAdd(arr, 3, 1); // arr[3]++ atomic
// ByteBuffer VarHandle — useful for parsing binary protocols
VarHandle intView = MethodHandles.byteArrayViewVarHandle(int[].class,
java.nio.ByteOrder.BIG_ENDIAN);
byte[] buf = new byte[8];
intView.set(buf, 0, 0x01020304); // write int at offset 0
int magic = (int) intView.get(buf, 0); // read int from offset 0
System.out.printf("%08X%n", magic); // 01020304
// Similarly for ByteBuffer:
VarHandle bbView = MethodHandles.byteBufferViewVarHandle(int[].class,
java.nio.ByteOrder.LITTLE_ENDIAN);
Practical Use Cases
VarHandlejava.util.concurrentLinkedTransferQueueConcurrentHashMapForkJoinPoolsun.misc.UnsafeAtomicReferenceAtomicInteger
// Use case 1: Lock-free counter (replaces AtomicInteger for inline use)
class LockFreeCounter {
private int count = 0;
private static final VarHandle COUNT;
static {
try {
COUNT = MethodHandles.lookup()
.findVarHandle(LockFreeCounter.class, "count", int.class);
} catch (ReflectiveOperationException e) { throw new ExceptionInInitializerError(e); }
}
public int incrementAndGet() {
return (int) COUNT.getAndAdd(this, 1) + 1;
}
public int get() { return (int) COUNT.getVolatile(this); }
}
// Use case 2: Lazy initialization (double-checked locking without volatile field)
class LazyHolder<T> {
private T value;
private static final VarHandle VALUE;
static {
try {
VALUE = MethodHandles.lookup()
.findVarHandle(LazyHolder.class, "value", Object.class);
} catch (ReflectiveOperationException e) { throw new ExceptionInInitializerError(e); }
}
private final java.util.function.Supplier<T> supplier;
LazyHolder(java.util.function.Supplier<T> supplier) { this.supplier = supplier; }
@SuppressWarnings("unchecked")
public T get() {
T v = (T) VALUE.getAcquire(this);
if (v == null) {
synchronized (this) {
v = (T) VALUE.getAcquire(this);
if (v == null) {
v = supplier.get();
VALUE.setRelease(this, v);
}
}
}
return v;
}
}
AtomicIntegerAtomicReference