CAS

1:CAS是什么?

比较并交换 (compareAndSwap),是一条CPU并发原语,是乐观锁技术的一种实现,当多个线程尝试使用CAS同时修改同一个变量时,只有其中一个线程能修改变量的值,而其它线程都失败,失败的线程并不会被挂起或者结束,

而是继续尝试(循环比较)修改,直到修改成功

源码一览:

    public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
//获取数据 var5
= this.getIntVolatile(var1, var2);
      //比较数据 v1和v2找到值 和v5做比较 }
while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); return var5; }

v1是当前对象,v2是内存地址偏移量,通过v1和v2可以查找到一个精确的数据并拷贝到自己线程工作区域内,然后根据这个数据和v5进行比较,只要是一致的,那么执行v5 + v4

当线程被切换过之后,那么此时,v5还是v5,但是v1和v2的值却不一定等于v5了,因为已经切换过其他线程,其他线程可能对这个v1和v2已经做了修改,如果做了修改,那么将继续循环比较,如此往复,直到成功

优点:

  1:不加锁,通过unSafe类解决数据一致性问题

  2:直接操作主内存 不存在数据不一致问题

  3:通过自旋锁方式,对数据进行比较和交换

缺点:

  1:自旋锁对cpu开销特别大

  2:只能保证一个共享变量的原子操作

  3:导致ABA问题

ABA问题:

  两个线程通过cas操作同一个变量,当T1线程和T2线程都获得主内存X的时候,T1的期望值是A想要修改成B,但是T2线程快速的将主内存A修改为任意值,然后又将任意值修改回A,此时T1再次获取主内存 值的时候发现 还是A就做了修改。表面上程序是正常运行的,但是这个过程中这个值是存在多变的,不保证这个过程没有问题!

代码示例:

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


public class test {
    static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);

    public static void main(String[] args) {

        new Thread(() -> {
            atomicReference.compareAndSet(100,101);
            atomicReference.compareAndSet(101,100);
        },"t1").start();


        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(2);
                
                //在本次修改之前 其实数据已经被修改过,但是此处只做第一次和最后一次的比较,发现100=100,所以就直接继续修改完成
                System.out.println(atomicReference.compareAndSet(100, 2000) + " \t getVaue:" + atomicReference.get());
            }catch (Exception d){
                d.printStackTrace();
            }
        },"t2").start();
    }
}

 

解决ABA问题:

  通过 AtomicStampedReference来添加版号作为修改的依据,如下代码,只要ABA发生,数据则修改失败!

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


public class test {
    static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);
    static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100,1);
    public static void main(String[] args) {

        new Thread(() -> {
            atomicReference.compareAndSet(100,101);
            atomicReference.compareAndSet(101,100);
        },"t1").start();


        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(2);
                System.out.println(atomicReference.compareAndSet(100, 2000) + " \t getVaue:" + atomicReference.get());
            }catch (Exception d){
                d.printStackTrace();
            }
        },"t2").start();

        //处理ABA问题
        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(3);
            }catch (Exception e){
                e.printStackTrace();
            }
            atomicStampedReference.compareAndSet(100,200,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
            atomicStampedReference.compareAndSet(200,100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
        },"t3").start();


        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp();

            try {
                TimeUnit.SECONDS.sleep(3);
            }catch (Exception e){
                e.printStackTrace();
            }

            boolean b = atomicStampedReference.compareAndSet(100, 500, stamp, stamp + 1);

            System.out.println(Thread.currentThread().getName() + " \t 版本号:" + stamp + ",当前版本号: "+atomicStampedReference.getStamp()+"修改结果:" + b
                    + ",实际值是:" + atomicStampedReference.getReference());
        },"t4").start();

    }
}

 

posted @ 2022-02-26 20:32  鸭猪是的念来过倒  阅读(196)  评论(0编辑  收藏  举报