CAS

本文介绍了CAS(比较与交换)技术,它是一种乐观锁技术。多个线程同时用CAS更新同一变量时,仅一个线程能成功,失败线程不挂起可再尝试。CAS包含V内存值、A期望值、B新值三个参数,仅当A与V相同时,才将V修改为B。

CAS(compare And Swap)即比较与交换,CAS是乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其他线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以在此尝试。它包含三个参数:V内存值,A期望值,B要修改的新值。当且仅当期望值A与内存值V相同时,将内存值V修改为B,否则什么都不做。

08-06
### CAS与Java内存管理的关系 CAS(Compare-And-Swap)是一种无锁的原子操作机制,广泛应用于Java的并发编程中,尤其是在Java的`java.util.concurrent.atomic`包中的`AtomicInteger`、`AtomicReference`等类中。CAS操作通过直接调用底层CPU指令实现,确保在多线程环境下对共享变量的操作是原子性的,从而避免使用锁带来的性能开销和死锁问题。 CAS在Java中是通过`sun.misc.Unsafe`类提供的本地方法实现的,这些方法调用了JNI(Java Native Interface)来与底层硬件交互。具体来说,CAS操作依赖于处理器提供的原子指令,如`cmpxchg`(在x86架构上)等,这些指令在执行时不会被中断,从而保证了操作的原子性[^3]。 以下是CAS操作的三个核心方法,它们用于对不同类型的数据进行比较并交换: ```java public final native boolean compareAndSwapObject(Object o, long offset, Object expected, Object x); public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x); public final native boolean compareAndSwapLong(Object o, long offset, long expected, long x); ``` 这些方法接受四个参数:对象实例、字段的内存偏移量、期望值和新值。只有当当前值等于期望值时,才会将新值写入内存,否则不做任何操作。这种方式在Java并发编程中被广泛用于实现非阻塞算法[^2]。 ### CAS在Java内存管理中的作用 在Java内存管理中,CAS主要用于实现线程安全的共享变量操作,尤其是在`Atomic`系列类中。例如,`AtomicInteger`类中的`incrementAndGet()`方法内部就是使用CAS来实现自增操作的。这种方式避免了传统锁机制带来的上下文切换和阻塞开销。 以下是一个使用`AtomicInteger`的示例: ```java import java.util.concurrent.atomic.AtomicInteger; public class CasExample { private static AtomicInteger count = new AtomicInteger(0); public static void increment() { count.incrementAndGet(); // 使用CAS实现的原子自增 } public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { for (int i = 0; i < 1000; i++) { increment(); } }); Thread t2 = new Thread(() -> { for (int i = 0; i < 1000; i++) { increment(); } }); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println("Final count is : " + count.get()); } } ``` 在上述代码中,`incrementAndGet()`方法内部使用了CAS操作,确保在多线程环境下对`count`变量的自增操作是线程安全的。 ### CAS的ABA问题及其解决方案 尽管CAS操作是原子的,但它仍然存在一个经典的**ABA问题**。即:如果一个变量的值从A变为B,再变回A,此时CAS操作会认为该变量没有发生变化,从而允许更新。然而,这种情况下变量实际上已经发生了变化,只是最终值与原始值相同。 ABA问题的典型场景是一个使用CAS实现的无锁链表栈结构。假设栈顶为A,线程T1读取A的下一个节点为B,准备将栈顶替换为B。在T1执行CAS之前,线程T2将A和B弹出,压入了C和D,最后又将A压入栈顶。此时栈顶仍然是A,但B的下一个节点已经是null。T1执行CAS时发现栈顶仍然是A,于是将栈顶设置为B,但此时B的next为null,导致C和D被丢失[^5]。 解决ABA问题的常见方法是引入**版本号(Versioning)**或**时间戳(Timestamp)**。每次修改变量时,不仅修改其值,还同时更新版本号。这样即使值变回A,版本号也会不同,从而避免ABA问题。Java中提供了`AtomicStampedReference`类来实现带有版本号的原子引用操作。 示例如下: ```java import java.util.concurrent.atomic.AtomicStampedReference; public class ABAExample { private static AtomicStampedReference<Integer> atomicRef = new AtomicStampedReference<>(1, 0); public static void main(String[] args) { Thread t1 = new Thread(() -> { boolean success = atomicRef.compareAndSet(1, 2, 0, 1); System.out.println("Thread 1 CAS result: " + success); }); Thread t2 = new Thread(() -> { boolean success = atomicRef.compareAndSet(1, 2, 0, 1); System.out.println("Thread 2 CAS result: " + success); success = atomicRef.compareAndSet(2, 1, 1, 2); // 修改回1,并更新版本号 System.out.println("Thread 2 revert result: " + success); }); t2.start(); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } t1.start(); } } ``` ### 相关问题 CAS机制虽然高效,但在Java内存管理中也存在一些挑战和限制,例如: - **ABA问题**:如前所述,可以通过引入版本号来解决。 - **自旋开销**:CAS失败后通常会重试,这可能导致线程长时间自旋,浪费CPU资源。 - **只能保证单个变量的原子性**:对于多个变量的操作,CAS无法保证整体的原子性,需要结合其他机制(如锁或事务内存)来实现。 ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值