1. 简介

Java 9 为开发者带来了许多实用新特性,其中之一就是 变量句柄(Variable Handles) API —— 即 java.lang.invoke.VarHandle 类。本文将深入探讨这个强大的工具。

2. 什么是变量句柄?

简单来说,变量句柄就是对变量的类型化引用。这个变量可以是:

  • 数组元素
  • 类的实例字段
  • 类的静态字段

VarHandle 类提供了在特定条件下对变量的读写访问能力。它有两个关键特性:

  • ✅ 不可变性:创建后无法修改
  • ✅ 无可见状态:不暴露内部状态
  • ❌ 不可被继承

每个 VarHandle 都包含:

  • 泛型类型 T:表示该句柄引用的所有变量的类型
  • 坐标类型列表 CT:用于定位变量的坐标表达式类型列表(可能为空)

核心目标:为字段和数组元素提供标准化的原子操作,替代 java.util.concurrent.atomicsun.misc.Unsafe 的底层操作。典型场景包括原子字段递增等。

3. 创建变量句柄

先定义一个测试类,包含不同类型的 int 变量:

public class VariableHandlesUnitTest {
    public int publicTestVariable = 1;
    private int privateTestVariable = 1;
    public int variableToSet = 1;
    public int variableToCompareAndSet = 1;
    public int variableToGetAndAdd = 0;
    public byte variableToBitwiseOr = 0;
}

3.1. 最佳实践约定

强烈建议将 VarHandle 声明为 static final 字段,并在静态块中显式初始化。通常使用对应字段名的大写形式作为句柄名。

Java 内部 AtomicReference 的实现就是典范:

private volatile V value;
private static final VarHandle VALUE;
static {
    try {
        MethodHandles.Lookup l = MethodHandles.lookup();
        VALUE = l.findVarHandle(AtomicReference.class, "value", Object.class);
    } catch (ReflectiveOperationException e) {
        throw new ExceptionInInitializerError(e);
    }
}

3.2. 公共变量的句柄

使用 findVarHandle() 创建公共变量的句柄

VarHandle PUBLIC_TEST_VARIABLE = MethodHandles
  .lookup()
  .in(VariableHandlesUnitTest.class)
  .findVarHandle(VariableHandlesUnitTest.class, "publicTestVariable", int.class);

assertEquals(1, PUBLIC_TEST_VARIABLE.coordinateTypes().size());
assertEquals(VariableHandlesUnitTest.class, PUBLIC_TEST_VARIABLE.coordinateTypes().get(0));

坐标类型列表包含一个元素:VariableHandlesUnitTest 类本身。

3.3. 私有变量的句柄

访问私有变量需要 privateLookupIn() 方法

VarHandle PRIVATE_TEST_VARIABLE = MethodHandles
  .privateLookupIn(VariableHandlesUnitTest.class, MethodHandles.lookup())
  .findVarHandle(VariableHandlesUnitTest.class, "privateTestVariable", int.class);

assertEquals(1, PRIVATE_TEST_VARIABLE.coordinateTypes().size());
assertEquals(VariableHandlesUnitTest.class, PRIVATE_TEST_VARIABLE.coordinateTypes().get(0));

⚠️ 与 Java 8 的 Unsafe 或反射 setAccessible() 相比:

  • ✅ 句柄方案更高效
  • ✅ 不局限于特定实例
  • ❌ 需要更谨慎的权限控制

3.4. 数组句柄

除了字段句柄,还可以创建特定类型数组的句柄

VarHandle arrayVarHandle = MethodHandles.arrayElementVarHandle(int[].class);

assertEquals(2, arrayVarHandle.coordinateTypes().size());
assertEquals(int[].class, arrayVarHandle.coordinateTypes().get(0));

坐标类型列表包含两个元素:int[] 数组类型和数组索引类型。

4. 调用句柄方法

大多数 VarHandle 方法接受可变数量的 Object 参数。这导致:

  • ❌ 编译时无类型检查
  • ✅ 运行时动态验证
  • ⚠️ 参数类型/数量错误会抛 WrongMethodTypeException

典型方法签名要求:

  • get():至少 1 个参数(定位变量)
  • set():至少 2 个参数(定位变量 + 新值)

5. 句柄访问模式

VarHandle 方法分为五大访问模式:

5.1. 读取访问

提供带内存语义的变量读取,包括:

  • get()
  • getAcquire()
  • getVolatile()
  • getOpaque()

使用示例:

assertEquals(1, (int) PUBLIC_TEST_VARIABLE.get(this));

5.2. 写入访问

提供带内存语义的变量写入,包括:

  • set()
  • setOpaque()
  • setVolatile()
  • setRelease()

使用示例:

VARIABLE_TO_SET.set(this, 15);
assertEquals(15, (int) VARIABLE_TO_SET.get(this));

5.3. 原子更新访问

提供原子更新操作,如 compareAndSet()

VARIABLE_TO_COMPARE_AND_SET.compareAndSet(this, 1, 100);
assertEquals(100, (int) VARIABLE_TO_COMPARE_AND_SET.get(this));

参数说明:

  • 第一个坐标参数:定位变量
  • oldValue:期望值
  • newValue:更新值

5.4. 数值原子更新

提供数值原子操作(如 getAndAdd):

int before = (int) VARIABLE_TO_GET_AND_ADD.getAndAdd(this, 200);

assertEquals(0, before);
assertEquals(200, (int) VARIABLE_TO_GET_AND_ADD.get(this));

操作流程:先返回当前值,再原子性增加指定值。

5.5. 位原子更新

提供位运算原子操作(如 getAndBitwiseOr):

byte before = (byte) VARIABLE_TO_BITWISE_OR.getAndBitwiseOr(this, (byte) 127);

assertEquals(0, before);
assertEquals(127, (byte) VARIABLE_TO_BITWISE_OR.get(this));

⚠️ 访问模式冲突会抛 IllegalAccessException(例如对 final 字段调用 set())。

6. 内存排序语义

所有句柄方法都支持特定的内存排序效果,分为四类:

排序模式 特性说明
Plain 保证 32 位内基本类型/引用的原子性,无额外内存排序约束
Opaque 保证原子性 + 对同一变量的访问顺序一致性
Acquire/Release 在 Opaque 基础上,确保 Acquire 读操作在匹配的 Release 写操作之后执行
Volatile 完全内存排序(与 volatile 关键字语义一致)

⚠️ 关键陷阱:访问模式会覆盖变量原有的内存语义。例如:

  • 即使变量声明为 volatile,调用 get() 仍是 Plain 读取
  • 开发者必须显式选择 getVolatile() 才能保持 volatile 语义

7. 总结

变量句柄是 Java 9 引入的底层操作工具,主要特点:

  • ✅ 统一替代 Unsafe 和原子类的底层操作
  • ✅ 提供更细粒度的内存控制
  • ❌ 使用复杂度高,仅推荐在需要极致性能的场景使用

除非必要,否则建议优先使用 java.util.concurrent.atomic 包中的高级抽象。完整示例代码可在 GitHub 获取。


原始标题:Java 9 Variable Handles Demystified

« 上一篇: Flyway回调机制详解
» 下一篇: BDDMockito 使用指南