问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

并发编程中的CAS操作:ABA问题及其解决方案

创作时间:
作者:
@小白创作中心

并发编程中的CAS操作:ABA问题及其解决方案

引用
CSDN
1.
https://m.blog.csdn.net/qq_30500575/article/details/145471262

在并发编程中,CAS(Compare and Swap)操作是一种常见的原子操作,用于实现无锁编程。然而,CAS操作也存在一些缺点,其中最典型的就是ABA问题。本文将详细介绍CAS的缺点、ABA问题的产生原因,并提供具体的解决方案。

CAS 的缺点

CAS(Compare and Swap)是一种原子操作,用于实现无锁编程。其基本思想是:比较内存位置的当前值与预期值,如果相等,则将内存位置的值更新为新值。CAS操作通常包含三个参数:内存位置、预期值和新值。

尽管CAS操作在很多场景下都非常有效,但它也存在一些缺点:

  • CPU 空转的问题:当多个线程同时尝试更新同一个变量时,可能会导致大量线程在循环中空转,浪费CPU资源。
  • 锁 饥饿:如果一个线程长时间持有锁,其他线程可能会长时间无法获得锁,导致锁饥饿问题。

ABA 问题

ABA问题是指在并发编程中,一个变量的值从A变为B,然后再变回A,这种情况下,CAS操作可能会误判为变量值没有发生变化,从而导致错误的结果。

ABA 问题案例

下面通过一个具体的代码示例来说明ABA问题:

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class ABADemo {
    // 演示ABA
    static AtomicInteger atomicInteger = new AtomicInteger(100);

    public static void main(String[] args) {
        new Thread(() ->{
            atomicInteger.compareAndSet(100,101);
            atomicInteger.compareAndSet(101,100);
        },"t1").start();

        try {
            TimeUnit.SECONDS.sleep(10);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        new Thread(() ->{
            boolean b = atomicInteger.compareAndSet(100, 102);
            System.out.println(Thread.currentThread().getName()+"\t"+"修改成功后与否:"+b+"\t"+atomicInteger.get());
        },"t2").start();
    }
}

输出结果表明,线程t2成功地将变量值从100修改为102,但实际上变量值已经经历了从100到101再到100的变化。这种情况下,CAS操作无法检测到中间的变化,从而导致安全性问题。

解决办法

为了解决ABA问题,可以采用版本号机制。具体来说,每次对变量进行修改时,都会更新一个版本号。CAS操作在比较变量值的同时,也会比较版本号,只有当值和版本号都匹配时,才会执行更新操作。

升级加固-版本号机制

下面通过代码示例说明如何使用版本号机制解决ABA问题:

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicStampedReference;

public class ABADemo {
    // 演示ABA
    static AtomicInteger atomicInteger = new AtomicInteger(100);
    static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100,1);

    public static void main(String[] args) {
        new Thread(()->{
            int stamp = atomicStampedReference.getStamp();
            System.out.println("当前版本号:"+stamp);
            atomicStampedReference.compareAndSet(100,101,stamp,stamp+1);
            int stamp2 = atomicStampedReference.getStamp();
            System.out.println("当前版本号:"+stamp2);
            atomicStampedReference.compareAndSet(101,100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
            int stamp3 = atomicStampedReference.getStamp();
            System.out.println("当前版本号:"+stamp3);
        },"t2").start();

        try {
            TimeUnit.SECONDS.sleep(10);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

通过引入版本号机制,每次对变量进行修改时都会更新版本号,从而避免了ABA问题。

高级示例

下面是一个更复杂的示例,展示了如何在多线程环境下使用版本号机制避免ABA问题:

import java.util.concurrent.atomic.AtomicStampedReference;

public class AvoidABADemo {
    public static void main(String[] args) {
        AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);
        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName()  + "\t首次版本号:" + stamp); // 1
            // 暂停一会儿线程
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            atomicStampedReference.compareAndSet(100,  101, atomicStampedReference.getStamp(),  atomicStampedReference.getStamp()  + 1);
            System.out.println(Thread.currentThread().getName()  + "\t 2次版本号:" + atomicStampedReference.getStamp());
            atomicStampedReference.compareAndSet(101,  100, atomicStampedReference.getStamp(),  atomicStampedReference.getStamp()  + 1);
            System.out.println(Thread.currentThread().getName()  + "\t 3次版本号:" + atomicStampedReference.getStamp());
        }, "t3").start();
        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName()  + "\t 首次版本号:" + stamp); // 1
            // 暂停一会儿线程,获得初始值100和初始版本号1,故意暂停3秒钟让t3线程完成一次ABA操作产生问题
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            boolean result = atomicStampedReference.compareAndSet(100,  2019, stamp, stamp + 1);
            System.out.println(Thread.currentThread().getName()  + "\t" + result + "\t" + atomicStampedReference.getReference());
        }, "t4").start();
    }
}

在这个示例中,线程t3先将变量值从100修改为101,再修改回100,同时更新版本号。线程t4在等待3秒钟后尝试将变量值从100修改为2019,但由于版本号不匹配,修改操作失败,从而避免了ABA问题。

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号