CAS中ABA问题的解决

本文详细解释了CAS(Compare and Swap)原理及其在并发编程中的应用,重点讨论了ABA问题的成因和解决策略,介绍了原子引用的概念及Java中的AtomicStampedReference。通过实例说明了如何通过版本号防止ABA问题,确保数据一致性。
  1. 什么是CAS?

    Compare and Swap (比较交换),使用CAS来保证线程的对变量的原子操作,避免被其他线程所干扰。

  2. CAS原理:其中包含三个参数

    1. 要修改的变量

    2. 变量的预期值

    3. 要更性的参数

      在更新数据时,CAS先判断变量中的值与预期值,如果一致则更新为新值,如果变量的值与预期值不 一致,表示当前操作被其他线程干扰,此时线程不会阻塞而是自旋等待(自旋锁),重复以上操作,CAS操作即 使没有锁,也可以发现其他线程对当前线程的干扰,并进行恰当的处理。

      CAS在Java层面虽然没有使用锁保证原子性,但是在底层汇编对“比较替换”进行加锁保证其原子性操作。

    4. CAS可能存在的问题

      1. CPU开销较大

        在并发较高的情况下,如果许多线程反复尝试更改一个变量,却一直更新不成功,循环往复,会给CPU带来很大压力。看可以通过设置默认自旋次数来减少CPU消耗。

      2. 不能保证代码原子性

        CAS机制所保证的只是一个变量的原子性操作,而不能保证整个代码块的原子性。比如需要保证3个变量共同进行原子性的更新,就不得不使用synchronized了。

      3. ABA问题

        这是CAS机制存在的最大的问题。

  3. ABA问题

    什么是ABA问题?

    -在使用CAS进行变量的原子性操作时,先要获取一个预期值,然后再判断预期值与实际的值是否相同,如果相同则继续修改变量。由于CAS操作没有使用重量级锁,如果获得一个预期值后,实际的值被其他线程篡改为其他值,然后又被改回到原有的值,对于原线程来说,实际值和预期值是一样的,但实际上值已经被修改过。这样的问题就是ABA问题

    例如:t1线程进行CAS操作,获取的预期值为"A",在t1线程进行"比较交换"前,t2线程进入并将变量的值 由"A"改为"B",然后又重新改回到"A"并退出,此时对于t1线程来说,实际值和预期值都是"A",就可以 做"比较并交换"操作,但实际上,实际值"A"已被修改过。

  4. 如何解决ABA问题

    上述例子看似没有问题,但是结合实际就会发现其问题所在,举例如下:

    ,假设有一个遵循CAS的取款机,小明去取款机取款,此使账户余额100元,小明要取款50,但是由于故障取款机执行了两次取款线程:

    • 线程1:取款50,余额50

    • 线程2:取款50,余额50(失败)

      理想状况下之只能有一个线程执行成功,另一个线程执行失败,进入等待,但是此时小明的母亲给小明转账50元,此时:

    • 线程3:存款50,余额100元

      由于线程3执行成功,小明账户余额又到达了100元,此时线程2,通过cas比较交换,发现前后数据一致,所以可以进行数据改写.如下:

    • 线程2:取款50,余额50

      此时不难发现小明的余额少了50元.这就是ABA问题.那么我们如何解决呢?

    • 我们可以使用CAS中的"原子引用"来解决ABA问题.

      原子引用是基于乐观锁的思想,为CAS中的每个操作添加一个版本号,每次执行成功之后,都会对版本号进行+1操作,每次更新数据之前都会对比版本号,观察是否有其他线程进行干扰.如果版本号一制则继续,不一致进入自旋.

      Java中提供了AtomicStampedReference类来实现原子引用并可以设置版本号

### ABA问题的本质 ABA问题CAS(Compare and Swap)操作中的一种典型问题,指的是一个值在被其他线程修改后,又被改回了原来的值。这种情况下,CAS操作会误认为值没有被改变,从而导致数据一致性问题。这种问题的核心在于内存中值的变化过程未被记录,无法反映其版本变化的历史[^3]。 ### ABA问题解决方案 为了解决ABA问题,通常需要引入额外的机制来记录值的变化过程。以下是两种常见的解决方案: 1. **版本号机制** 通过为值添加一个版本号,每次值发生变化时,版本号也会递增。在进行CAS操作时,不仅比较值是否相等,还需要比较版本号是否一致。这种方法确保了即使值被改回,只要版本号不同,CAS操作也会失败,从而避免误判。例如,Java中的`AtomicStampedReference`类就实现了这种机制,通过记录值的版本来解决ABA问题[^2]。 ```java // 示例代码:使用 AtomicStampedReference 解决 ABA 问题 import java.util.concurrent.atomic.AtomicStampedReference; public class ABAExample { public static void main(String[] args) { // 初始值和版本号 AtomicStampedReference<Integer> stampedReference = new AtomicStampedReference<>(100, 0); Thread t1 = new Thread(() -> { int expectedValue = 100; int newValue = 50; int stamp = stampedReference.getStamp(); // 获取当前版本号 if (stampedReference.compareAndSet(expectedValue, newValue, stamp, stamp + 1)) { System.out.println("T1 更新值成功: " + newValue + ",版本号: " + stampedReference.getStamp()); } }); Thread t2 = new Thread(() -> { int expectedValue = 100; int newValue = 200; int stamp = stampedReference.getStamp(); // 获取当前版本号 if (stampedReference.compareAndSet(expectedValue, newValue, stamp, stamp + 1)) { System.out.println("T2 更新值成功: " + newValue + ",版本号: " + stampedReference.getStamp()); } }); t1.start(); t2.start(); } } ``` 2. **标志位机制** 另一种解决方案是使用标志位,通过记录值是否被修改过的状态来解决ABA问题Java中的`AtomicMarkableReference`类通过一个布尔值标志位来标记值是否被修改过。这种方法适用于只需要记录值是否发生变化的场景[^2]。 ```java // 示例代码:使用 AtomicMarkableReference 解决 ABA 问题 import java.util.concurrent.atomic.AtomicMarkableReference; public class ABAExample { public static void main(String[] args) { // 初始值和标志位 AtomicMarkableReference<Integer> markableReference = new AtomicMarkableReference<>(100, false); Thread t1 = new Thread(() -> { int expectedValue = 100; int newValue = 50; boolean[] mark = new boolean[1]; mark[0] = markableReference.isMarked(); // 获取当前标志位 if (markableReference.compareAndSet(expectedValue, newValue, mark[0], !mark[0])) { System.out.println("T1 更新值成功: " + newValue + ",标志位: " + markableReference.isMarked()); } }); Thread t2 = new Thread(() -> { int expectedValue = 100; int newValue = 200; boolean[] mark = new boolean[1]; mark[0] = markableReference.isMarked(); // 获取当前标志位 if (markableReference.compareAndSet(expectedValue, newValue, mark[0], !mark[0])) { System.out.println("T2 更新值成功: " + newValue + ",标志位: " + markableReference.isMarked()); } }); t1.start(); t2.start(); } } ``` ### 总结 通过引入版本号或标志位机制,可以有效解决CAS操作中的ABA问题。这些机制通过记录值的变化历史,确保了在并发环境下数据的一致性和正确性。尽管这些方法会带来额外的内存和计算开销,但在需要严格数据一致性的场景下,它们是不可或缺的解决方案[^3]。 ---
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值