CAS

本文深入探讨了Java.util.concurrent包如何基于比较并交换(CAS)操作实现乐观锁,对比了乐观锁与悲观锁(synchronized)的区别,并介绍了原子类在多线程环境中的作用。

java.util.concurrent包完全建立在CAS之上的,没有CAS就不会有此包。

java.util.concurrent包中借助CAS实现了区别于synchronouse同步锁的一种乐观锁。

独占锁是一种悲观锁,synchronized就是一种独占锁,会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁。而另一个更加有效的锁就是乐观锁。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。

Java.util.concurrent.atomic包中几乎大部分类都采用了CAS操作。需考虑ABA问题。

JDK1.5的原子包:java.util.concurrent.atomic

这个包里面提供了一组原子类。其基本的特性就是在多线程环境下,当有多个线程同时执行这些类的实例包含的方法时,具有排他性,即当某个线程进入方法,执行其中的指令时,不会被其他线程打断,而别的线程就像自旋锁一样,一直等到该方法执行完成,才由JVM从等待队列中选择一个另一个线程进入,这只是一种逻辑上的理解。实际上是借助硬件的相关指令来实现的,不会阻塞线程(或者说只是在硬件级别上阻塞了)。

public final intincrementAndGet() {

   for (;;) {

       int current = get();//volatile 

       int next = current + 1;

       if (compareAndSet(current, next))

           return next;

   }

}死循环一定能保证在新增发生时一定时同步的。CAS可以在无锁的情况发生原子操作。

如servlet一般无状态的类是线程安全的。

### 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无法保证整体的原子性,需要结合其他机制(如锁或事务内存)来实现。 ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值