ABA问题的解决--CAS

CAS的三个缺点

第一个缺点:while循环会造成一直匹配不到值的循环操作,这也是CAS的一个缺点

第二个缺点:只能对一个对象进行原子操作,并不能对一块代码进行原子操作

第三个缺点就是接下来要讲的ABA问题
ABA问题的产生:
ABA问题就是加入主存中的值为A
(1)两个线程都会去主存里拿到一份值A
(2)t1线程由于执行时间短,两秒,所以执行了又A->B->A的操作
(3)由于JMM原理,主存中也是同样执行了这样的操作
(4)线程t2在执行完之后看到主存中的值还是A就进行交换,但其实已经被修改过了,又改回来了
在这里插入图片描述
解决:
加上一个时间戳,每改变一次,自增一次,相当于版本号
使用原子引用类AtomicStampedReference

    public AtomicStampedReference(V initialRef, int initialStamp) {
        pair = Pair.of(initialRef, initialStamp);
    }

参数一还是对象,参数二则是一个初始版本号
然后每次改变主存中的值就会改变版本号,这样就有效 避免了ABA问题的发生
解决ABA问题demo如下

package com.wsx.aba;

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

public class SloveAba {
    static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);
    static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100,1);

    public static void main(String[] args) {
        System.out.println("ABA问题的产生");
        new Thread(()->{
            atomicReference.compareAndSet(100,101);
            atomicReference.compareAndSet(101,100);
            System.out.println(Thread.currentThread().getName()+"\t"+atomicReference.get());
        },"t1").start();

        new Thread(()->{
            try {
                //为了让t1完成一次ABA操作
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            atomicReference.compareAndSet(100,2019);

            System.out.println(Thread.currentThread().getName()+"\t"+atomicReference.get());
        },"t2").start();

        //主线程睡眠等待上限操作完成
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("ABA问题的解决");


        new Thread(()->{
            int stamp = atomicStampedReference.getStamp();
            //这里睡一秒是为了t3线程和t4线程的起点一致,版本号都为1
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"\t最新版本号"+atomicStampedReference.getStamp());
            //ABA带版本号操作
            atomicStampedReference.compareAndSet(100,101,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
            System.out.println(Thread.currentThread().getName()+"\t最新版本号"+atomicStampedReference.getStamp());
            atomicStampedReference.compareAndSet(101,100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
            System.out.println(Thread.currentThread().getName()+"\t最新版本号"+atomicStampedReference.getStamp());

        },"t3").start();

        new Thread(()->{
            int stamp = atomicStampedReference.getStamp();
            //这里睡两秒是为了让t3线程完成ABA操作
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //这里的stamp是默认没被修改的版本号,如果被其他线程修改过那么这里版本号就开始落后,然后修改失败
            boolean result = atomicStampedReference.compareAndSet(100, 2019, stamp, stamp + 1);

            System.out.println(Thread.currentThread().getName()+"\t最新版本号"+atomicStampedReference.getStamp());
            System.out.println(Thread.currentThread().getName()+"\t是否改动成功"+result);
            System.out.println(Thread.currentThread().getName()+"\t最新值"+atomicStampedReference.getReference());
        },"t4").start();
    }

}



原创文章 28 获赞 26 访问量 3368

猜你喜欢

转载自blog.csdn.net/Avril___/article/details/104868965