VarHandle and Memory Ordering

Naveen AnamaneniUpdated · 4 min read

VarHandle, in java.lang.invoke (Java 9), is a typed reference to a variable, such as a field or an array element, that lets you read and write it with chosen memory-ordering guarantees and perform atomic operations like compare-and-set on it. It is the modern, supported replacement for the internal sun.misc.Unsafe class, and the JDK's own concurrent classes are built on it. Most application code never needs it; it is for library authors and for understanding how java.util.concurrent works. In this lesson, you will learn how to create a VarHandle, use its atomic methods, and what the access modes mean.

Creating a VarHandle

Syntax
static final VarHandle COUNT;
static {
    try {
        COUNT = MethodHandles.lookup()
            .findVarHandle(Counter.class, "count", int.class);
    } catch (ReflectiveOperationException e) {
        throw new ExceptionInInitializerError(e);
    }
}

MethodHandles.arrayElementVarHandle(int[].class) creates one for array elements.

Access Modes

From weakest (fastest) to strongest:

ModeMethodsGuarantee
Plainget, setLike a normal field: no ordering or visibility promises
OpaquegetOpaque, setOpaqueEach access really happens and is eventually visible; no ordering with other variables
Acquire / ReleasegetAcquire, setReleaseA release write is seen, with everything written before it, by a matching acquire read
VolatilegetVolatile, setVolatileSame as a volatile field: a single total order
Atomic updatescompareAndSet, getAndAdd, getAndSetAtomic, with volatile ordering

Release/acquire is the key idea: a setRelease acts like a fence that "publishes" every earlier write, and a getAcquire that sees that value is guaranteed to see those earlier writes too. It's the same happens-before pattern as a volatile flag (see "Java Memory Model and Happens-Before"), but slightly cheaper on some CPUs.

Example: Atomic Updates and Release/Acquire Publishing

Let's see an example that counts with a VarHandle and publishes data with release/acquire.

Handles.java▶ Try it
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
public class Handles {
    int count;
    String message;
    boolean ready;
 
    static final VarHandle COUNT;
    static final VarHandle READY;
 
    static {
        try {
            MethodHandles.Lookup lookup = MethodHandles.lookup();
            COUNT = lookup.findVarHandle(Handles.class, "count", int.class);
            READY = lookup.findVarHandle(Handles.class, "ready", boolean.class);
        } catch (ReflectiveOperationException e) {
            throw new ExceptionInInitializerError(e);
        }
    }
 
    public static void main(String[] args) throws InterruptedException {
        Handles h = new Handles();
        try (ExecutorService pool = Executors.newFixedThreadPool(4)) {
            for (int t = 0; t < 4; t++) {
                pool.submit(() -> {
                    for (int i = 0; i < 50_000; i++) {
                        COUNT.getAndAdd(h, 1);
                    }
                });
            }
        }
        System.out.println("count = " + (int) COUNT.getVolatile(h));
 
        Thread reader = new Thread(() -> {
            while (!(boolean) READY.getAcquire(h)) {
                Thread.onSpinWait();
            }
            System.out.println("Reader sees: " + h.message);
        });
        reader.start();
        h.message = "Hello from Advitha";
        READY.setRelease(h, true);
        reader.join();
    }
}
Output
count = 200000
Reader sees: Hello from Advitha

Explanation: COUNT.getAndAdd(h, 1) atomically adds one to the plain int field, so four threads reach exactly 200,000 without AtomicInteger. Then the main thread writes message normally and publishes it with setRelease on ready. The reader spins with getAcquire until it sees true; acquire/release guarantees it then sees the message written before the release.

When to Use VarHandle

Common Mistakes

Mixing plain reads with release writes and expecting visibility. Pair setRelease with getAcquire (or stronger).

Getting the field type wrong in findVarHandle. It throws when the class initializes.

Forgetting the cast on the result. get methods return Object-typed values that must be cast, such as (int).

Practice

  1. Easy: Create a VarHandle for a long field and use getAndAdd to sum numbers from two threads.
  2. Harder: Use MethodHandles.arrayElementVarHandle(int[].class) to increment elements of a shared array atomically from four threads.

Quick check: VarHandle

4 questions
1What does a VarHandle refer to?
2Which pair publishes data like a volatile flag, but slightly cheaper?
3What did VarHandle replace?
4Which method adds atomically to a field through a VarHandle?

Key Takeaways

Was this tutorial helpful?

Lesson 422 of 482