VarHandle / Exchanger 内存操作源码
概述
VarHandle 与 Exchanger 是 JUC 中两个面向"内存与交换"的低层组件:
VarHandle(JDK 9):对字段/数组元素的类型安全原子访问句柄,统一替代AtomicIntegerFieldUpdater/Unsafe的手工操作,提供get/set/compareAndSet/getAndAdd等全套访问模式与内存排序变体。Exchanger:双线程(或多线程)无锁数据交换点——两个线程在一个同步点上互换数据,内部用单槽(slot)与多槽竞技场(arena)两级结构减少争用。
本文基于 OpenJDK 21 源码,拆解 VarHandle 的句柄创建与底层内存操作、Exchanger 的槽位交换算法。
核心源码解析
① VarHandle 的创建
java
public abstract class VarHandle {
// 通过 MethodHandles.Lookup 查找字段句柄
VarHandle vh = MethodHandles.lookup()
.findVarHandle(Entity.class, "fieldName", FieldType.class);
}findVarHandle(Class<?> recv, String name, Class<?> type)在Lookup的权限范围内定位字段,内部调用VarHandles.makeFieldHandle(field, lookup)创建字段句柄。- 类型安全:句柄的方法签名(接收者类型 + 值类型)在创建时确定,调用不匹配立即抛
ClassCastException/WrongMethodTypeException,比Unsafe的裸 offset 安全得多。 - 实现类是内部的具体变体:
VarHandleReferences(引用字段)、VarHandleInts/VarHandleLongs/VarHandleBooleans等(按字段类型分派),共享访问模式枚举AccessMode。 - 数组元素句柄:
MethodHandles.arrayElementVarHandle(int[].class)可创建数组元素的句柄;VarHandle.VarHandleDesc提供可序列化描述(配合condy常量动态直接加载句柄)。
② VarHandle.get(Object recv) / set(Object recv, T value) 的读写
java
public final Object get(Object... args) { ... } // 签名多态方法
public final void set(Object... args) { ... }- 签名多态:
VarHandle的访问方法都是native修饰的签名多态方法(与MethodHandle.invoke同机制),Object... args只是表象,实际按创建时的AccessMode分派到具体实现。 MethodHandle链接:每个访问模式在句柄创建时生成对应的MethodHandle(VarHandleForm缓存),JIT 可整体内联。- 底层
Unsafe:字段句柄的读写最终落到Unsafe.getObject/Unsafe.putObject(按字段类型选择getInt/getLong/putInt等)——利用objectFieldOffset算出的内存偏移直接访问,绕开反射。 - 读走
getObjectVolatile(默认访问模式带 volatile 语义)保证可见性;普通GET模式对应getObject(plain)。
③ VarHandle.compareAndSet(Object recv, T expected, T newValue) 的 CAS
java
public final boolean compareAndSet(Object... args) { ... }
// 底层(引用字段实现,VarHandleReferences)
boolean compareAndSet(Object[] objs) {
return Unsafe.compareAndSwapObject(objs[0], offset, objs[1], objs[2]);
}compareAndSwapObject(Object obj, long offset, Object expected, Object x)是Unsafe的 CAS 入口,落到硬件cmpxchg(compare-and-exchange)指令——x86 上带lock前缀保证原子性。- 语义:仅当目标位置当前值 == expected 时写入 x,返回是否成功;失败不写入(调用方自旋重试)。
- 相比
AtomicIntegerFieldUpdater,VarHandle的 CAS 同样基于Unsafe,但不依赖反射 Field 对象(创建时解析 offset),热路径更短。
④ VarHandle.getAndSet(Object recv, T newValue) 的原子交换
java
public final Object getAndSet(Object... args) { ... }
// 底层:Unsafe.getAndSetObject 内部就是 CAS 循环
final Object getAndSet(Object[] objs) {
Object o = objs[0];
Object v = objs[1];
for (;;) {
Object prev = Unsafe.getObjectVolatile(o, offset);
if (Unsafe.compareAndSwapObject(o, offset, prev, v))
return prev;
}
}- CAS 循环实现"读-改-写":
getAndSet反复"读旧值 → CAS 尝试"直到成功,返回旧值。 - x86 上
Unsafe.getAndSetObject有专门的lock xchg硬件支持(单指令完成),JDK 用 intrinsic 映射;通用实现回退到 CAS 循环。 - 同理
getAndAdd/getAndBitwiseOr等用getAndAddInt系列或 CAS 循环实现,为并发计数器、状态机提供原语。
⑤ VarHandle 的内存排序语义
AccessMode 按排序强度划分:
| 模式族 | 模式 | 语义 |
|---|---|---|
| plain | GET / SET | 普通读写,无排序保证 |
| opaque | GET_OPAQUE / SET_OPAQUE | 原子但不保证可见性顺序 |
| acquire / release | GET_ACQUIRE / SET_RELEASE | 读获取 / 写释放,配对形成临界区 |
| volatile | GET_VOLATILE / SET_VOLATILE | 全排序,等价 volatile 字段 |
| CAS 族 | COMPARE_AND_SET / COMPARE_AND_EXCHANGE / WEAK_COMPARE_AND_SET* | 读-比较-写,弱 CAS 可假失败 |
| 读改写 | GET_AND_ADD / GET_AND_BITWISE_* 及其 acquire/release 变体 | 原子复合操作 |
- 选择自由度:需要
volatile全排序用_VOLATILE;只需要单侧同步用acquire/release(如无锁队列的发布);不关心排序用 plain/opaque 换取性能。 WEAK_COMPARE_AND_SET:允许"假失败"(无理由返回 false),不可靠但更宽松,适合自旋算法。- JIT 会对
VarHandle访问模式做LockElision/ 指令级优化,_Opaque/_Acquire在 x86 上往往是空操作(x86 硬件本身 acquire 语义较强)。
⑥ Exchanger.exchange(V x) 的双线程数据交换
java
public V exchange(V x) throws InterruptedException {
Object v = doExchange(x, false, 0); // 无限等待
...
return (V) v;
}
private final Object doExchange(Object item, boolean timed, long ns) {
Node me = new Node(item); // 当前线程的交换节点
int index = participant.get(); // Participant(ThreadLocal)中的槽索引
for (;;) {
Object item2 = slotExchange(item, timed, ns); // ① 单槽路径
if (item2 == CANCEL) return doExchange(item, timed, ns); // 取消则重试
if (item2 != null) return item2; // ② 单槽交换成功
// ③ 单槽失败(争用大)→ arenaExchange 多槽路径
Object item3 = arenaExchange(item, timed, ns);
if (item3 != null) return item3;
...
}
}- 双路径设计:低争用时走
slotExchange(单个槽位,开销极小);检测到争用后升级到arenaExchange(多槽数组摊薄冲突)。 Participant extends ThreadLocal<Node>:每个线程复用专属Node(含index槽位索引),减少分配与伪共享。- 交换本质是"两个线程互赋
item":等待方发布自己的值,匹配方 CAS 拿走并回写自己的值,双方同时获得对方数据。
⑦ Exchanger.Slot 的自旋 + CAS
java
private final Object slotExchange(Object item, boolean timed, long ns) {
Node p = participant.get();
Thread t = Thread.currentThread();
if (t.isInterrupted()) return CANCEL;
for (Node q = slot;;) {
if (q == null) { // ① 槽空:发布自己
if (item != null && p.item == null) p.item = item;
if (slot.compareAndSet(null, p)) break; // CAS 占用单槽
continue; // 竞争失败重试
}
if (q.isData != (item != null)) { // ② 槽上有反模式节点:交换
Object v = q.item;
if (v != CANCEL) {
if (slot.compareAndSet(q, null)) { // ③ 清空槽位
...
q.item = item; // 互相写值
...
return v; // 返回对方的值
}
}
...
}
// ④ 槽被占但未匹配:自旋等待
... LockSupport.parkNanos(this, ns); // 自旋后 park
}
// 占用成功后等待配对
for (;;) {
if (item != null && p.item == item) { ... spinWait ... }
...
}
}- 占用与匹配分离:
CAS(slot, null, p)抢占槽位后自旋等待匹配方;匹配方通过CAS(slot, q, null)清槽并回写值。 - 自旋阈值:短等待
Thread.onSpinWait()忙等,超过阈值(约 1000ns)后LockSupport.parkNanos阻塞——平衡响应与 CPU 占用。 CANCEL特殊标记:中断/超时后节点值被替换为 CANCEL,其他线程检测到即跳过,保证无死锁。
⑧ Exchanger 的多槽扩展(arena)
java
private final Object arenaExchange(Object item, boolean timed, long ns) {
Node[] a = arena; // Slot[] 数组
int alen = a.length;
Node p = participant.get();
int i = p.index; // 当前线程的槽索引
for (;;) {
Node q = a[i];
if (q == null) { // ① 槽位空:CAS 占用
if (a[i].compareAndSet(null, p)) { ... 等待/交换 ... }
} else if (q.isData != (item != null)) { // ② 反模式:交换
...
}
// ③ 冲突处理:随机移动索引 / 扩容 arena
if (++crashes >= alen) {
// arena 扩容:数组翻倍(不超过最大索引),摊薄争用
...
}
i = (i + (alen >> 1)) & (alen - 1); // 伪随机跳到其他槽
}
}Slot[]数组:arena 是 2 的幂大小的Node[](受ASHIFT限制),每个线程从自己的index开始尝试,冲突时跳到其他槽位((i + alen/2) & (alen-1)步长),避免多个线程挤在一个槽。- 扩容:连续冲突计数
crashes超过数组长度时 arena 翻倍,让更多线程分散到不同槽位——多线程场景下显著降低 CAS 争用。 - 与单槽的关系:
arenaExchange是slotExchange的失败升级路径;单槽适合 2 线程交换,arena 适合"多线程两两配对"的并发场景。
总结
| 组件 | 核心结构 | 关键机制 |
|---|---|---|
VarHandle | AccessMode + 内部 MethodHandle | findVarHandle 创建、Unsafe 偏移读写、cmpxchg CAS |
| VarHandle 排序 | 五种强度变体 | plain / opaque / acquire / release / volatile |
Exchanger | slot + arena[] + Participant | CAS(slot, null, node) 占槽、反模式匹配交换 |
| Exchanger 扩展 | arena 多槽 | 冲突随机跳槽 + 数组翻倍扩容 |
VarHandle 把 Unsafe 的内存操作"正规化":类型安全、访问模式完整、可由 JIT 深度优化;Exchanger 则展示了无锁交换的完整形态——单槽低开销、多槽抗争用、自旋降级到 park。两者分别从"内存访问"与"线程配对"两个角度,补齐了 JUC 无锁工具箱的底层能力。