Contents
- Obtaining a VarHandle
- Atomic Operations
- Memory Ordering Modes
- Array and ByteBuffer VarHandles
- Practical Use Cases
A VarHandle is obtained via MethodHandles.lookup() and findVarHandle(). It is typically stored as a static final field for maximum JIT optimization.
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);
}
}
A VarHandle for a private field must be obtained from within the same class using MethodHandles.lookup(). To access fields in another class, use MethodHandles.privateLookupIn() — but that class must open the relevant package to the caller's module.
VarHandle provides compareAndSet(), getAndAdd(), and getAndSet() with configurable memory ordering — from plain (no guarantee) through acquire/release up to full volatile semantics — making it more flexible than AtomicInteger while remaining JIT-friendly and inlineable. This allows you to implement lock-free data structures that precisely express the minimum ordering needed for correctness, avoiding unnecessary memory fences on every operation.
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
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;
}
}
Using weaker ordering modes (plain, opaque) incorrectly can cause data races and subtle bugs. Only use them if you have deep understanding of the Java Memory Model. For most code, volatile semantics (via volatile fields or AtomicInteger) are the right choice.
MethodHandles.arrayElementVarHandle() creates a VarHandle that targets individual elements of an array by index. This enables atomic operations — compareAndSet, getAndAdd, and others — on specific array slots without locking the entire array, which is essential for building efficient, fine-grained concurrent data structures such as striped counters or concurrent ring buffers.
// 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);
VarHandles are used internally throughout java.util.concurrent — LinkedTransferQueue, ConcurrentHashMap, and ForkJoinPool all rely on them instead of sun.misc.Unsafe. They are the preferred choice in new library and framework code that needs atomic operations on fields of existing classes without wrapping those fields in AtomicReference or AtomicInteger — which would change the class layout and potentially affect serialization or memory footprint.
// 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;
}
}
For most application-level code, prefer AtomicInteger, AtomicReference, and their siblings — they're built on VarHandle internally and have a cleaner API. Use VarHandle directly when you need custom atomic operations on your own fields or the finest-grained memory ordering control.