1. 简介
Java 9 为开发者带来了许多实用新特性,其中之一就是 变量句柄(Variable Handles) API —— 即 java.lang.invoke.VarHandle
类。本文将深入探讨这个强大的工具。
2. 什么是变量句柄?
简单来说,变量句柄就是对变量的类型化引用。这个变量可以是:
- 数组元素
- 类的实例字段
- 类的静态字段
VarHandle
类提供了在特定条件下对变量的读写访问能力。它有两个关键特性:
- ✅ 不可变性:创建后无法修改
- ✅ 无可见状态:不暴露内部状态
- ❌ 不可被继承
每个 VarHandle
都包含:
- 泛型类型
T
:表示该句柄引用的所有变量的类型 - 坐标类型列表
CT
:用于定位变量的坐标表达式类型列表(可能为空)
核心目标:为字段和数组元素提供标准化的原子操作,替代 java.util.concurrent.atomic
和 sun.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 获取。