【多线程学习】ABA问题

「这是我参与11月更文挑战的第24天,活动详情查看:2021最后一次更文挑战

理解ABA问题

image.png

现在有两个线程都拿到了A=1的值,Thread2要比Thread1操作更快,迅速的拿到和旧值相比较相等就把1换成了3,又再次操作将3改为了1,这个时候工作内存中的Thread1和主存中的1相比较的确是相等的,但是实际上Thread1被欺骗了,相比较的1其实是Thread2改过了的值

这就是所谓的ABA问题,一个值原来是A,变成了B,然后又变成了A,那么在CAS检查的时候会发现没有改变,但是实质上它已经发生了改变

ABA问题会造成什么影响

1.如果只是单纯的数值数据,无业务关联逻辑,没有影响

2.如果数据是有业务含义的就需要处理,比如最重要的涉及到钱的问题

如何解决ABA问题

解决ABA问题的方案就是加上版本号,在每一个变量身上都加上版本号,每次改变版本号都加1

image.png

核心的思想就是,判断这个值是否更新过如果更新过,就不允许再次被操作。Java中的解决方案是AtomicStampedReference,一个带版本号的原子引用类,使用一个版本号,每次修改都将版本号增加,也就是版本号机制

实现demo

模拟两个线程去更改初始值

public class TestAba {

    public static void main(String[] args) {
        AtomicStampedReference<Integer> stamp = new AtomicStampedReference(1,1);

        new Thread(() -> {
            System.out.println("线程A 还未改变,版本号为: " + stamp.getStamp());

            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            boolean one = stamp.compareAndSet(1, 2, stamp.getStamp(), stamp.getStamp() + 1);
            System.out.println("线程A 第一次改变是否成功: " + one);
            System.out.println("线程A 第一次改变,版本号为: " + stamp.getStamp());

            boolean two = stamp.compareAndSet(2, 1, stamp.getStamp(), stamp.getStamp() + 1);
            System.out.println("线程A 第二次改变是否成功: " + two);
            System.out.println("线程A 第二次改变,版本号为: " + stamp.getStamp());

        },"A").start();

        new Thread(() -> {
            System.out.println("线程B 还未改变,版本号为: " + stamp.getStamp());

            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            boolean one = stamp.compareAndSet(1, 3, stamp.getStamp(), stamp.getStamp() + 1);
            System.out.println("线程B 第一次改变是否成功: " + one);
            System.out.println("线程B 第一次改变,版本号为: " + stamp.getStamp());

        },"B").start();
        
    }

}
复制代码

查看运行结果,可以看到在A线程更改成功过后,虽然初始的已经被A线程改回去了但是,线程B再去更改的时候没有更改成功,因为此时的初始值是已经被线程A改过了,版本号变为了3而不是1,线程B就会更改不成功

image.png

代码过程遇到的问题

刚开始随意给初始值设置了一个值2021,AtomicStampedReference<Integer> stamp = new AtomicStampedReference(2021,1); 发现修改就不会像预期一样成功

出现改问题的原因:Integer使用了对象缓存机制,默认范围是-128~127,只只要超过了这一个值,就一定会分配一个新的内存空间导致指向的值实际上并不是同一个值

所以需要注意:如果使用泛型是包装类,要注意对象的引用问题