搜索站内内容

← 返回文章列表

JUC 学习 03:CAS、synchronized、AQS 与 ThreadLocal

按原始笔记的顺序整理 CAS、ABA、锁升级、AQS、ReentrantLock 与 ThreadLocal 的常见面试问题。

JasonBackendJavaJUCCASAQSThreadLocal约 1,150 字大约 4 分钟
云雾在层叠的山岭之间流动

这部分对应 JUC 笔记中从原子操作到锁框架的一段。它们的共同点不是「越底层越高级」,而是都在处理共享状态的竞争;差别在于冲突发生时选择重试、阻塞,还是把状态隔离到线程本地。

CAS 算法是什么

CAS 是 Compare And Swap:比较内存位置的当前值与预期值,只有相等时才写入新值。JDK 的原子类用它实现无锁更新。一个典型重试过程如下:

for (;;) {
  int oldValue = counter.get();
  if (counter.compareAndSet(oldValue, oldValue + 1)) {
    break;
  }
}

它避免了无冲突时的阻塞,但冲突高时会反复自旋并消耗 CPU。LongAdder 通过多个 Cell 分散写热点来提升高竞争计数的吞吐,不过读取总和要汇总多个 Cell,因此不适合每次读取都要求严格一致的余额或库存。

CAS 的 ABA 问题

若值从 A 变成 B 后又变回 A,普通 CAS 只看当前值,会误以为从未变化。这就是 ABA。栈顶、链表指针等需要识别版本变化的场景,可以使用带版本戳的原子引用,或在业务对象上维护明确版本号。

不要把 ABA 泛化为「所有 CAS 都不安全」。对只关心当前数值、且中间变化不影响业务语义的计数器,ABA 不构成问题;先定义状态的语义,再决定是否需要版本。

synchronized 的底层原理和锁升级

synchronized 依赖对象监视器(monitor)。字节码层面会出现 monitorentermonitorexit;对象头中的 Mark Word 会保存锁相关信息。JVM 会根据竞争情况使用不同实现,核心目标是让无竞争或低竞争路径尽量轻,而竞争明显时再转向更重的协调。

面试里不宜把「偏向锁、轻量级锁、重量级锁」说成每一次加锁必须逐级走过的固定流程。更可靠的表达是:锁状态和实现受 JVM 版本与竞争情况影响,应用层更该关注临界区是否短、锁粒度是否合理、是否可能形成锁顺序环。

AQS 是什么

AbstractQueuedSynchronizer 用一个 state 表示同步状态,并以 FIFO 双向队列管理获取同步资源失败的线程。ReentrantLockSemaphoreCountDownLatch 等都在不同语义上复用这套排队与唤醒骨架。

原始 OneNote 笔记中的 AQS 同步队列示意

原始笔记图示:AQS 以同步状态与等待队列协调竞争线程。

这张图的关键不是记住节点字段,而是理解两条路径:拿到状态的线程继续执行;拿不到的线程排队并在合适时机被唤醒。共享模式与独占模式、可中断获取与超时获取,都建立在这个基本模型上。

ReentrantLock 和 synchronized 的区别

两者都能实现互斥和可重入。synchronized 语法简单,异常退出也会自动释放 monitor;ReentrantLock 提供 tryLock、可中断获取、公平策略以及多个 Condition,适合确实需要这些能力的场景。

不要因为 ReentrantLock API 更多就默认选择它。若没有超时、可中断或多个条件队列的实际需求,synchronized 的边界更小,也更不容易遗漏 unlock()。使用 ReentrantLock 时,释放必须放进 finally

lock.lock();
try {
  updateSharedState();
} finally {
  lock.unlock();
}

ThreadLocal 的原理和内存泄漏

ThreadLocal 把变量存到当前线程的 ThreadLocalMap 中,而不是存到 ThreadLocal 实例本身。Map 的 key 是弱引用,value 是强引用;在线程池里,工作线程长期存活,key 被回收后 value 仍可能留在 Entry 中,直到后续访问触发清理。这就是常说的 ThreadLocal 泄漏风险。

原始 OneNote 笔记中的 ThreadLocalMap 结构示意

原始笔记图示:线程持有 ThreadLocalMap,Entry 的 key 为弱引用。

因此在线程池、Web 容器、异步执行器中,set 后必须在 finallyremove。它适合保存一次调用链的上下文,例如 traceId 或临时格式器;不适合当成跨线程传递参数的全局变量。异步边界需要显式传递或使用受控的上下文传播机制。

小结

CAS 解决低冲突下的原子更新,锁解决多状态不变量与条件等待,ThreadLocal 解决线程内隔离。它们不是可互换的性能技巧,而是三种不同的共享状态建模方式。

FIELD NOTES / DISCUSS

文章讨论

读完后,欢迎留下你的补充、疑问或不同看法。

97 浏览

全部评论 (0)

正在读取评论…

    GUEST IDENTITY

    设置访客身份

    评论、回复与留言板将复用这份身份。

    选择头像