非阻塞同步依靠CPU原子指令(如CAS)在用户态实现,无锁、无挂起、无中间态;典型代表是AtomicInteger.incrementAndGet(),需手动循环重试并每次重新读取最新值,推荐使用VarHandle而非Unsafe。

非阻塞同步到底靠什么实现
它不依赖 synchronized 或 ReentrantLock 这类需要内核介入的锁机制,而是直接用 CPU 提供的原子指令(比如 CAS、LL/SC)在用户态完成状态变更。关键在于:所有操作要么全成功,要么完全不生效,没有中间态,也不挂起线程。
常见错误现象是误以为“不用 synchronized 就是非阻塞”——其实 volatile 读写只是保证可见性,不构成同步;而 AtomicInteger.incrementAndGet() 才是典型非阻塞同步,背后调的是 Unsafe.compareAndSwapInt()。
- 使用场景:高竞争但临界区极短的操作,比如计数器、无锁队列节点入队
- 参数差异:
AtomicReference.compareAndSet(expected, updated)要求你显式传入期望值,失败后需自行重试,不是“自动重试” - 性能影响:避免了线程上下文切换开销,但在强竞争下可能因反复 CAS 失败导致 CPU 空转(spin)
CAS 循环重试怎么写才不出错
手动写 CAS 循环时,最容易漏掉对返回值的判断,或者把期望值写成固定值而不是每次从变量里重新读取。
正确做法是用 get() 获取当前值 → 计算新值 → compareAndSet(oldValue, newValue),失败就重来。JDK 的 AtomicInteger.getAndUpdate() 内部就是这么干的。
立即学习“Java免费学习笔记(深入)”;
- 常见错误:写成
if (cas(old, new)) break;却没更新old,导致无限失败 - 必须每次循环都调用
get()或getOpaque()重新读取最新值,不能复用上一轮的局部变量 - 注意 ABA 问题:值从 A→B→A,CAS 会误判成功;必要时用
AtomicStampedReference带版本戳
为什么 varHandle.compareAndSet() 比 Unsafe 更推荐
Unsafe 是 JDK 内部 API,不同版本行为不一致,且从 JDK 9 开始默认禁止反射调用;VarHandle 是官方公开的、标准化的底层原子操作接口,语义清晰,还能跨平台适配不同 CPU 的内存序。
比如在 x86 上 VarHandle.compareAndSet() 编译为 lock cmpxchg,在 ARM 上则用 ldxr/stxr 序列,你不用操心。
- 兼容性影响:JDK 9+ 中
Unsafe.compareAndSwap*已被标记为@Deprecated(since="9", forRemoval=true) - 性能差异几乎为零,但
VarHandle支持更细粒度的内存语义控制,如weakCompareAndSetPlain() - 初始化成本略高(需
MethodHandles.lookup().findVarHandle()),但只做一次,不影响运行时
非阻塞结构真比锁快?看这三点再决定
不是所有场景都适合非阻塞。盲目替换 synchronized 可能更慢,甚至引发活锁。
真正值得上非阻塞的地方,得同时满足:临界区代码少于 10 行、单次操作耗时远小于一次线程调度(通常
- 容易踩的坑:用
ConcurrentLinkedQueue替代ArrayBlockingQueue,结果发现吞吐没涨反降——因为前者无锁但指针跳转多,缓存不友好 - 调试困难:CAS 失败不会抛异常,只能靠日志或 JFR 观察
sun.misc.Unsafe::compareAndSwap*调用频次 - 复杂点往往不在算法本身,而在内存可见性边界和重排序约束,比如忘了用
VarHandle.setRelease()保证后续写入不被重排到 CAS 之前










