← 每日面试题 学习看板

如何实现无锁化编程?

面试题/00java/02Java 并发编程/09编程实践与应用场景/如何实现无锁化编程?.md

面试记录 2026-10-17

题目:如何实现无锁化编程?

难度:medium

标签:java/concurrency-practice, java/concurrency, general/knowledge

评分:⭐⭐⭐☆☆(3/5)

我的回答

无锁编程的核心手段包括CAS,原子类,无共享状态等; CAS的核心是比较交换,以及原子性的单更新,通过比较预想值,当前值和目标值来实现对于属性更新时确保是从自己知道的改到自己目标的,但是存在ABA问题也就是当前的这个A和自己预期的A本质并不是一个,是其他线程修改后的结果; 原子类是通过cpu包装的原子操作进行对象的简单变更,但是只能应对简单的对象变更增加,并不能应对太复杂的操作; 无共享状态则是基于多线程没有交集,也就不会有线程安全问题,但是共享状态是很多业务所必需的,应用场景相对比较受限;

点评

优点:CAS/原子类/无共享状态三条都答到,ABA 问题点出且解释准确,也意识到"只能处理单变量、简单操作"的边界。

不足:没给具体手段(AtomicInteger/AtomicReference/LongAdder/Unsafe/VarHandle);没说 ABA 的解法(版本号 AtomicStampedReference);没提高并发下的 自旋开销。

补充:无锁 = 乐观重试 + 原子指令;适用"单变量、低到中竞争",高竞争时 LongAdder 优于 AtomicLong,再高就走分段/有锁。

完整讲解(权威版)

最后更新:2026-10-26 · 基于第 2 次作答

L1 · 30 秒速记

结论:无锁化 = CAS + 原子类 + 无共享状态 三条主线;核心是乐观重试 + 原子指令替代阻塞锁。 关键词:CAS cmpxchg · LongAdder 分段 · AtomicStampedReference 解 ABA 边界:只保单变量原子性;ABA 需版本号;高竞争自旋反而浪费 CPU

L2 · 核心讲解

核心考点定位:无锁的手段清单 + 各自边界 + ABA 治理。

机制链(3 环):

  1. 传统锁(synchronized/ReentrantLock)→ 阻塞等待 → CPU 上下文切换开销高 → 在高并发、低竞争场景效率低
  2. 无锁化思路 = 乐观重试 + 原子指令 → CAS(compare-and-swap)用 CPU 的 cmpxchg 指令原子完成"读-比-写" → 失败重试而非阻塞
  3. 但 CAS 有 ABA 问题(值 A→B→A 后 CAS 误判)→ 用 AtomicStampedReference 加版本号解决 → 高竞争时自旋浪费 CPU → 改用 LongAdder 分段或锁

证据锚点(去重 ≥10):

对比与权衡:

方案原子性竞争开销ABA 防护适用
synchronized / ReentrantLock✅阻塞,高竞争 O(n)不需要高竞争、复合操作
CAS(AtomicXxx)✅(单变量)低竞争无自旋❌单变量低竞争
AtomicStampedReference✅(单变量)低竞争无自旋✅单变量需 ABA 防护
LongAdder✅(弱一致 sum)分段无自旋N/A高并发计数
volatile(读写)不保证原子内存屏障开销N/A可见性

常见误区:

  1. 错误:"CAS 一定比锁快" → 正确:低竞争时快,高竞争时自旋反而浪费 CPU,不如锁 → 为什么:CAS 是乐观重试,竞争大时重试次数暴涨
  2. 错误:"CAS 能解决所有原子性问题" → 正确:只保单变量,复合操作要 AtomicReference 打包或直接加锁 → 为什么:两个变量的原子性无法用 CAS 单独保证
  3. 错误:"ABA 问题很少出现" → 正确:链表节点复用、版本号更新等场景常见 → 为什么:ABA 隐蔽,容易被误判为幂等
  4. 错误:"LongAdder 和 AtomicLong 完全一样" → 正确:LongAdder 分段 Cell 分散热点,sum() 是弱一致;AtomicLong 是全局 CAS → 为什么:高并发计数用 LongAdder 吞吐高得多
  5. 错误:"ThreadLocal 就是无锁" → 正确:ThreadLocal 是"无共享状态",不是无锁化手段本身 → 为什么:无锁化是 CAS 类,ThreadLocal 是避开共享

适用边界:

延伸追问(≥3)

  1. CAS 的 ABA 怎么解决?
  1. LongAdder 为什么比 AtomicLong 快?
  1. CAS 一定自旋吗?竞争大怎么办?
  1. Unsafe 和 VarHandle 有什么区别?

面试者参考回答(400–700 字)

【结论】 无锁化编程本质是"用乐观重试 + 原子指令替代阻塞锁",三条主线:CAS + 原子类 + 无共享状态。

【三点】

第一,核心手段是 CAS 和原子类,CAS 靠 CPU 的 cmpxchg 加锁前缀保证原子,失败就自旋重试。它解决的是单变量的原子更新,复合操作要用 AtomicReference 打包。

第二,ABA 是 CAS 最大的坑——值 A→B→A 后 CAS 会误判为未变。用 AtomicStampedReference 加版本号解决,AtomicMarkableReference 用布尔标记。高并发计数场景用 LongAdder,它分段 Cell 分散热点,比 AtomicLong 快得多(sum() 是弱一致)。

第三,还有无共享状态的思路——不可变对象、ThreadLocal,从根上避开竞争。

【边界】 三点提醒:一是 CAS 只保单变量原子性,复合操作要 AtomicReference 或加锁;二是高竞争时 CAS 自旋反而浪费 CPU,不如 LongAdder 或退回到锁;三是 ABA 隐蔽,链表复用等场景常见,必须用版本号防护。

面试记录 2026-10-26(第 2 次)

题目:如何实现无锁化编程?

难度:medium

标签:java/concurrency-practice, java/concurrency, general/knowledge, difficulty/medium

评分:⭐⭐⭐☆☆(3/5)

练习次数:2

我的回答

无锁化编程方面有多种实现方式;首先可以规避共享对象的变更,没有多线程共享的资源也就没有锁的必要了;相关的有 ThreadLocal 的方案,和所有对象都在线程栈内部不做状态共享;另外就是通过 CAS 使用比较交换的方式,验证当前要变动更新的资源是我预期的资源,而没被其他线程污染,也可以实现无锁化的操作,不过多数这种只应对一个字段;另外就是原子命令,这个命令执行就一下不会有线程能插入进来,比如原子类的各种加减操作。

点评

优点:

不足:

完整讲解见 2026-10-17 首次记录(本文件上方)

与 2026-10-17 首次作答的对比

维度2026-10-17(首次)2026-10-26(第 2 次)
三条主线✅✅
ABA 问题⚠️ 提到❌ 未提
ABA 解法(AtomicStampedReference)❌ 未提❌ 未提
LongAdder❌ 未提❌ 未提
Unsafe / VarHandle❌ 未提❌ 未提
复合操作❌ 未提⚠️ 只说"单字段"
评分3/53/5

结论:两次作答在 ABA 问题上连续失分——这是 Java 并发高频考点,必须专项订正。ABA 的记忆点:"AtomicStampedReference 版本号 / AtomicMarkableReference 布尔标记"。