深度解析:为什么 AtomicReference.getAndSet 是非阻塞的?
在并发编程中,我们需要严格区分 “逻辑上的原子竞争” 与 “操作系统层面的线程阻塞”。
1. 物理层面:硬件原子指令 (Non-blocking)
getAndSet 的底层并不依赖操作系统的锁(Mutex)或信号量,而是直接对应 CPU 的硬件指令(如 x86 架构下的 LOCK XCHG)。
- 执行状态:调用该方法时,线程始终处于 RUNNING 状态,CPU 并没有停止工作。
- 指令周期:该操作通常在几十到几百个时钟周期内完成(纳秒级)。
- 对比阻塞:
- 传统锁:如果获取失败,操作系统会进行 上下文切换(Context Switch),将线程挂起并剥夺其 CPU 执行权。这一过程涉及数万个时钟周期。
- Atomic:CPU 只是在硬件层级对总线或缓存行进行了微小的“串行化”处理,线程无需离开 CPU。
2. 逻辑层面:乐观型分流 (Fail-fast)
SegmentPool 利用 getAndSet 实现了一种 “旋转门” 式的非阻塞算法。
// 线程执行到这一行时,无论成败,都会立即返回并继续向下执行
val first = firstRef.getAndSet(LOCK)
if (first === LOCK) {
// 路径 A:发现“门”被占用了。
// 线程【不等待】、 【不重试】、 【不睡眠】。
// 立即执行备选逻辑:新建一个 Segment。
return Segment()
} else {
// 路径 B:成功拿到了“门”里的值。
}
3. 核心差异对比
| 特性 | AtomicReference.getAndSet | synchronized / ReentrantLock |
|---|---|---|
| 线程状态 | 始终 RUNNING | 可能变为 BLOCKED 或 WAITING |
| 系统开销 | 极低(纳秒级,仅硬件指令) | 较高(涉及内核态切换、上下文保存) |
| 等待策略 | 立即撤退(Fail-fast) | 排队死等(Blocking) |
| 资源权衡 | 时间优先:宁可多分配内存也要换取延迟 | 空间优先:宁可等待也要复用现有资源 |
4. 结论:SegmentPool 的性能哲学
在 SegmentPool 中,getAndSet(LOCK) 扮演的是一个 “独占旗标”。
“能拿就拿,不能拿就走。”
这种设计确保了在高并发压力下,SegmentPool 永远不会成为导致线程卡顿的瓶颈。它成功地将一个可能导致阻塞的“锁问题”,转化为了一个简单的“逻辑分流问题”。
💡 深度总结
在 SegmentPool 的整个生命周期中:
- 初始化阶段:由于
object的类加载机制,存在一次性的 JVM 线程级阻塞。 - 运行阶段:全链路 非阻塞。没有任何操作会导致线程交出 CPU 执行权,从而实现了极致的响应速度。