解释 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
包下提供了一系列原子类来支持原子操作,例如 AtomicInteger
、AtomicLong
、AtomicReference
等。这些类内部通过调用 Unsafe
类的原子性方法实现了 CAS 操作。
比较 CAS 和 Synchronized 的优缺点。
- CAS(非阻塞同步)
- 优点:避免了线程阻塞,线程调度的开销较小,适用于竞争不激烈的场景,可以减少上下文切换的成本。
- 缺点:只能保护单个共享变量的原子操作,存在 ABA 问题,且在激烈竞争下自旋会增加 CPU 开销。
- Synchronized(阻塞同步)
- 优点:编码简单,JVM 优化(如偏向锁、轻量级锁)使得其性能逐渐提升,可以保护代码块或对象,解决多变量同步问题。
- 缺点:在高并发场景下可能导致大量线程阻塞,增加上下文切换的成本,效率低于非阻塞算法。