CAS

作者: 念䋛 | 来源:发表于2021-06-23 22:36 被阅读0次

    锁按照粒度分为悲观锁和乐观锁
    (1)悲观锁:顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。synchronized和ReentrantLock都是悲观锁.
    (2)乐观锁: 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,CAS就是乐观锁最好的体现.

    CAS(Compare And Swap) 比较并交换,在项目中的都是配合循环来实现乐观锁
    在java中cas的使用需要Unsafe类,看名字知道是非安全类,不建议使用,所以在平时很少单独的使用cas,都是使用已经包装好的类,比如JUC中的atomic包下的类.
    AtomicInteger atomicInteger = new AtomicInteger ();


    image.png

    atomicInteger.getAndIncrement ()方法源码

    public final int getAndAddInt(Object var1, long var2, int var4) {
        var1是对象本身
    var2 是修改值在内存中的地址
    var5 要修改的值
    var4 1
    int var5;
        do {
    //从主内存中获取最新的值到工作内存
            var5 = this.getIntVolatile(var1, var2);
    //修改值,并同步到主内存,如果不相等返回false,继续while循环,如果相等则加一并同步到内存
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
    
        return var5;
    }
    

    Cas的缺点

    1. Cas和自旋配合使用实现了乐观锁,会出现多个线程同时执行的时候,while多次循环,依然不能保证用户内存和主内存数值一致,这样会增加cpu的使用率,不过是不会出现死循环的现象.
    2. Cas只是对一个变量操作实现了原子性,不会对一段代码实现原子性.
    3. Cas会出现ABA问题
      ABA问题


      image.png

      虽然中间间隔的时间非常短,也会出现ABA问题,那么什么是ABA问题
      比如线程1中var5从主内存中获取变量值为 10,代码向下执行到while判断,var5和主内存中的10是否相等,如果相等将10+1,同步主内存和用户内存,
      如果
      线程1:var5获取变量为10
      线程2:一个完整的cas,将变量减一为9
      线程3:一个完整的cas,将变量加一为10
      线程1:while判断,主内存变量依然为10,与用户内存相等,将10+1,同步到主内存和用户内存
      虽然结果和我们得到的一样,但是变量中间经过了两次变化.
      那么如何解决ABA问题呢,
      AtomicStampedReference

    User user = new User ();
    User user1 = new User ();
    AtomicStampedReference<User> atomicStampedReference = new AtomicStampedReference (user,1);
    /**
     * 第一个参数老值
     * 第二个参数新值
     * 第三个参数老版本号
     * 第四个参数新版本号,注意加一的动作
     */
    boolean b = atomicStampedReference.compareAndSet (user, user1, atomicStampedReference.getStamp (),  atomicStampedReference.getStamp ()+1);
    User reference = atomicStampedReference.getReference ();
    System.out.println (reference == user1);
    AtomicInteger atomicInteger = new AtomicInteger ();
    atomicInteger.getAndIncrement ();
    

    initialRef:是变量user
    initialStamp:标签
    Pair就是一个静态内部类

    private static class Pair<T> {
        final T reference;
        final int stamp;
        private Pair(T reference, int stamp) {
            this.reference = reference;
            this.stamp = stamp;
        }
        static <T> Pair<T> of(T reference, int stamp) {
            return new Pair<T>(reference, stamp);
        }
    }
    

    compareAndSet

    public boolean compareAndSet(V   expectedReference,
                                 V   newReference,
                                 int expectedStamp,
                                 int newStamp) {
        Pair<V> current = pair;
        return
    //判断是否是一样的user
            expectedReference == current.reference &&
    //判断版本号是否一致
            expectedStamp == current.stamp &&
    //判断新的user和新的版本号
            ((newReference == current.reference &&
              newStamp == current.stamp) ||
    //如果主内存与当前内存的user和版本号相同,则将新的user和版本号更新主内存
    casPair(current, Pair.of(newReference, newStamp)));
    }
    

    手写一个cas 这里只是一个示例,因为unsafe类在实际生产中不建议使用

    public class CASTest {
        static Unsafe unsafe;
        public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
            //创建unsafe
            Class<?> clazz = Unsafe.class;
            Field f = clazz.getDeclaredField ("theUnsafe");
            f.setAccessible (true);
            unsafe = ( Unsafe ) f.get (clazz);
            User user = new User ();
            user.setName ("zhangsan");
            long headOffset = 0;
            try {
                //偏移量,compareAndSwapObject方法的第二个参数
                headOffset = unsafe.objectFieldOffset (User.class.getDeclaredField ("name"));
            } catch (NoSuchFieldException e) {
                e.printStackTrace ();
            }
            boolean b = unsafe.compareAndSwapObject (user, headOffset, "zhangsan", "lisi");
            System.out.println (user.getName ());
        }
    }
    
    

    结果为 lisi
    上面的代码共两点

    1. unsafe是如何创建的,在实际生产中是不建议使用
    2. User的name为zhangsan
      当执行compareAndSwapObject的时候,传入的zhangsan和user对象的name的zhangsan是相等的,所以比较并交换,当前线程的值和主内存的值比较,传入值和user的name值相等,将lisi赋值给user的name属性,就是当前线程和主线程的值都为lisi,而其他线程并不是lisi,需要其他线程在比较并交换的时候先将本线程的值和主内存比较,得到最新的值lisi
      如果user的name属性初始为zhangsan1,当比较并交换的时候, compareAndSwapObject传入的值为zhangsan 而user的name属性为zhangsan1不相等,那么当前线程的user在寄存器中的副本的值不变依然为zhangsan1 不会将lisi赋值到user的name属性,并返回b为false.

    相关文章

      网友评论

        本文标题:CAS

        本文链接:https://www.haomeiwen.com/subject/tiyiyltx.html