深度解析:为什么 `AtomicReference.getAndSet` 是非阻塞的?

2 阅读2分钟

深度解析:为什么 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.getAndSetsynchronized / ReentrantLock
线程状态始终 RUNNING可能变为 BLOCKEDWAITING
系统开销极低(纳秒级,仅硬件指令)较高(涉及内核态切换、上下文保存)
等待策略立即撤退(Fail-fast)排队死等(Blocking)
资源权衡时间优先:宁可多分配内存也要换取延迟空间优先:宁可等待也要复用现有资源

4. 结论:SegmentPool 的性能哲学

SegmentPool 中,getAndSet(LOCK) 扮演的是一个 “独占旗标”

“能拿就拿,不能拿就走。”

这种设计确保了在高并发压力下,SegmentPool 永远不会成为导致线程卡顿的瓶颈。它成功地将一个可能导致阻塞的“锁问题”,转化为了一个简单的“逻辑分流问题”。


💡 深度总结

SegmentPool 的整个生命周期中:

  1. 初始化阶段:由于 object 的类加载机制,存在一次性的 JVM 线程级阻塞
  2. 运行阶段:全链路 非阻塞。没有任何操作会导致线程交出 CPU 执行权,从而实现了极致的响应速度。