Interview
JUC

解释 Java 中的 CAS 操作是如何工作的。

CAS(Compare-And-Swap)操作通过比较内存中的某个值和一个预期值,如果相同,则将内存中的值更新为新的值。这一切操作都是原子性的,即不可中断的,确保了并发环境下的线安全。Java 中的 CAS 操作主要依赖于底层硬件的支持,如通过 Unsafe 类实现。

Java 中的 CAS 有哪些问题?如何解决?

  • ABA 问题:如果一个变量原来是 A,变成了 B,然后又变回 A,那么使用 CAS 进行比较时会认为没有变化,但实际上该变量已经被修改过两次。解决方法是使用版本号或时间戳,Java 的 AtomicStampedReference 类就是为了解决这个问题而设计的。
  • 自旋开销大:在高并发情况下,如果多个线程同时竞争更新同一个变量,则会导致大量线程进行自旋,消耗 CPU。解决方法是使用限制自旋次数或回退策略,如增加随机等待时间。
  • 只能保证单个变量的原子操作:如果需要对多个变量进行原子操作,CAS 就无能为力了。这种情况下,可以使用锁或者 AtomicReference 类来实现。

Java 中如何实现 CAS 操作?

Java 在 java.util.concurrent.atomic 包下提供了一系列原子类来支持原子操作,例如 AtomicIntegerAtomicLongAtomicReference 等。这些类内部通过调用 Unsafe 类的原子性方法实现了 CAS 操作。

比较 CAS 和 Synchronized 的优缺点。

  • CAS(非阻塞同步)
    • 优点:避免了线程阻塞,线程调度的开销较小,适用于竞争不激烈的场景,可以减少上下文切换的成本。
    • 缺点:只能保护单个共享变量的原子操作,存在 ABA 问题,且在激烈竞争下自旋会增加 CPU 开销。
  • Synchronized(阻塞同步)
    • 优点:编码简单,JVM 优化(如偏向锁、轻量级锁)使得其性能逐渐提升,可以保护代码块或对象,解决多变量同步问题。
    • 缺点:在高并发场景下可能导致大量线程阻塞,增加上下文切换的成本,效率低于非阻塞算法。