发布于2026-07-10 阅读(0)
扫一扫,手机访问
先看一个最核心的区别:compareAndExchange 总是返回当前值,不管交换成功与否;而 compareAndSet 只告诉你成功还是失败。这意味着,当你需要知道“为什么没换成功”——比如值被其他线程改了,或者压根没变——前者能直接给你答案。特别是实现带版本号的乐观锁时,不仅能知道更新失败,还能拿到当前版本号,用来决定下一步是重试、降级还是报错。

需要注意,compareAndExchange 并不是 compareAndSet 的加强版,而是语义上更中立的原子读-改-写操作。一个常见的错误是把它当 compareAndSet 用,却忽略了返回值:
handle.compareAndExchange(obj, expected, desired); // ❌ 忽略返回值,等于白调
正确的做法,是显式检查返回值是否等于 expected,而不是依赖布尔逻辑。
Ja va 的 VarHandle 允许为每次操作指定内存顺序,compareAndExchange 支持四种组合:Acquire、Release、AcquireRelease、SequentiallyConsistent。默认是 SequentiallyConsistent,性能开销最大;但如果你只关心“写后读可见”或“读后写不重排”,完全可以降级。
compareAndExchangeAcquire:保证后续读操作不会被重排到该操作之前compareAndExchangeRelease:保证前面的写操作不会被重排到该操作之后Acquire 读状态,再用 Release 更新),比全序模型减少 fence 指令,吞吐自然就上去了需要警惕的是,这些方法不是重载,而是独立的方法名——不存在“传参数选语义”的方式。用错方法名会导致语义不符合预期,而且编译器不会提醒你。
VarHandle 的 compareAndExchange 对引用类型字段(Object)完全支持,但对数组元素或嵌套字段(比如 obj.field.subfield)需要额外构造 handle。更关键的坑是:它不支持对 long/double 字段在 32 位 JVM 上的原子操作——虽然现代 JDK 基本都跑在 64 位上,但万一目标环境不明确,仍然可能触发 IllegalStateException。
典型的踩坑点包括:
static final VarHandle VH_LONG = MethodHandles.privateLookupIn(...).findVarHandle(...) 构造 handle 时,没校验 VH_LONG.isAccessModeSupported(VarHandle.AccessMode.COMPARE_AND_EXCHANGE)compareAndExchange ——直接抛 UnsupportedOperationExceptionsynchronized 和 compareAndExchange,造成语义冗余甚至死锁风险VarHandle.compareAndExchange 在 HotSpot 上最终会编译为相同的汇编指令(在 x86 上就是 cmpxchg),所以单次操作延迟几乎一致。真正的差异出现在 JIT 优化层面:
VarHandle(通过 MethodHandles.lookup().findVarHandle(...))会有首次调用开销,后续被内联后没有差别VarHandle(static final)可被完全内联,性能与 Unsafe 持平Unsafe.compareAndSwapInt 等方法在 JDK 9+ 已标记为 @Deprecated(forRemoval = true),部分 JVM(比如 GraalVM Native Image)根本不支持说到底,影响性能的不是方法本身,而是你能不能帮 JIT 看清模式:比如把 compareAndExchange 写在循环里却不加 final 或稳定类型提示,结果可能导致去优化。
核心思路很简单:别只盯着“能不能换成功”,重点是你拿到的旧值怎么用;内存语义不是越强越好,而是刚好够用——AcquireRelease 覆盖大多数状态机跃迁场景,过度使用 SequentiallyConsistent 会悄悄拖慢吞吐。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8