解决CAS算法的"ABA"漏洞问题

在将如何去解决去解决CAS的ABA问题时,我们先来说一下什么是CAS,CAS全称 compare-and-swap ,CPU并发原语。执行是连续的,不允许被中断,不会造成数据不一致问题。
CAS算法:它包含三个参数CAS(V,E,N): V表示要更新的变量,E表示预期值,N表示新值。仅当V值等于E值时,才会将V的值设为N,如果V值和E值不同,则说明已经有其他线程做了更新,则当前线程什么都不做。最后,CAS返回当前V的真实值。
CAS操作是抱着乐观的态度进行的,它总是认为自己可以成功完成操作。当多个线程同时使用CAS操作一个变量时,只有一个会胜出,并成功更新,其余均会失败。失败的线程不会被挂起,仅是被告知失败,并且允许再次尝试,当然也允许失败的线程放弃操作。基于这样的原理,CAS操作即使没有锁,也可以发现其他线程对当前线程的干扰,并进行恰当的处理。
然后我们再讲一讲ABA问题。
ABA问题: 假定在某个时刻某个线程从内存中取出A,然后在下个时刻准备更新这个值;在这个时间差内数据发生了改变;比如 假设线程1从内存中取出了A,线程2也从内存中取出了A,并且将值修改为B,最后又改为A,当线程1去更新值得时候发现内存中的数据和线程备份数据相同,可以更新;但是此时内存中的值其实发生了变化的,只不过又变回去了。
又比如现有一个用单向链表实现的堆栈,栈顶为A,这时线程T1已经知道A.next为B,然后希望用CAS将栈顶替换为B:head.compareAndSet(A,B);
在T1执行上面这条指令之前,线程T2介入,将A、B出栈,再pushD、C、A,,而对象B此时处于游离状态:
此时轮到线程T1执行CAS操作,检测发现栈顶仍为A,所以CAS成功,栈顶变为B,但实际上B.next为null,所以此时的情况变为:
其中栈中只有B一个元素,C和D组成的链表不再存在于堆栈中,平白无故就把C、D丢掉了。
我们用一段简单的代码来演示一下ABA问题:

public class ABA {
        private static AtomicInteger atomicInt = new AtomicInteger(100);
        public static void main(String[] args) throws InterruptedException {
                Thread T1 = new Thread(new Runnable() {
                        @Override
                        public void run() {
                                atomicInt.compareAndSet(100, 101);
                                atomicInt.compareAndSet(101, 100);
                        }
                });

                Thread T2 = new Thread(new Runnable() {
                        @Override
                        public void run() {
                                try {
                                        TimeUnit.SECONDS.sleep(1);
                                } catch (InterruptedException e) {
                                }
                                boolean c3 = atomicInt.compareAndSet(100, 101);
                                System.out.println(c3);
                        }
                });
                T1.start();
                T2.start();

        }
}

我们在线程T1中将atomicInt 的值改成101,又改回100,然后在线程T2中再次将atomicInt 的值改成101,由于线程T2是晚于线程T1执行的,所以T2拿到的atomicInt的值其实是已经变化了又变回来的值,然而它还是能够成功改变atomicInt的值。这就是ABA问题。
解决方式:由于CAS算法没有直接的使用锁;而是通过乐观锁的方式去控制并发的;而对于乐观锁而言一般都是操作+时间戳来控制每一次的版本号的;在JDK类库中,可以使用AutomicStampReference来解决。
然后我们来演示一下如何去使用AutomicStampReference来解决它。

public class ABA {
        private static AtomicInteger atomicInt = new AtomicInteger(100);
        private static AtomicStampedReference<Integer> atomicStampedRef = new AtomicStampedReference<Integer> (100, 0);

        public static void main(String[] args) throws InterruptedException {
          
                Thread refT1 = new Thread(new Runnable() {
                        @Override
                        public void run() {
                                try {
                                        TimeUnit.SECONDS.sleep(1);
                                } catch (InterruptedException e) {
                                }
                                atomicStampedRef.compareAndSet(100, 101, atomicStampedRef.getStamp(), atomicStampedRef.getStamp() + 1);
                                atomicStampedRef.compareAndSet(101, 100, atomicStampedRef.getStamp(), atomicStampedRef.getStamp() + 1);
                        }
                });

                Thread refT2 = new Thread(new Runnable() {
                        @Override
                        public void run() {
                                int stamp = atomicStampedRef.getStamp();
                                try {
                                        TimeUnit.SECONDS.sleep(2);
                                } catch (InterruptedException e) {
                                }
                                boolean c3 = atomicStampedRef.compareAndSet(100, 101, stamp, stamp + 1);
                                System.out.println(c3); 
                        }
                });

                refT1.start();
                refT2.start();
        }
}

由于在第一个线程中修改过atomicStampedRef的值,所以它的版本号也变更了,而在第二个线程中由于先拿到atomicStampedRef的版本号,然后二线程睡眠了一段时间,这段时间里atomicStampedRef的值已经变换过了,版本号就不同了,所以线程二的操作就失败了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值