• CAS


    乐观锁与悲观锁

    synchronized是悲观锁:
    
        每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁。
    
    CAS操作的就是乐观锁(乐观锁在Java中的使用,是无锁编程,常常采用的是CAS算法,典型的例子就是原子类):
    
        每次不加锁而是假设没有冲突而去完成某项操作,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据。
        如果因为冲突失败就重试,直到成功为止。
    
    
    悲观锁适合写操作非常多的场景,乐观锁适合读操作非常多的场景
    

    CAS机制(Compare And Swap)

    CAS是一种系统原语,原语属于操作系统用语范畴,是由若干条指令组成的,用于完成某个功能的一个过程。
    并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题
    
    Java中CAS操作的执行依赖于Unsafe类的方法,Unsafe类中的方法都直接调用操作系统底层资源执行相应任务
    
        //Unsafe类中的getAndAddInt方法
        public final int getAndAddInt(Object o, long offset, int delta) {
            int v;
            do {
                v = getIntVolatile(o, offset);
            } while (!compareAndSwapInt(o, offset, v, v + delta));
            return v;
        }
    
        getAndAddInt()通过一个while循环不断的重试更新要设置的值,直到成功为止,底层调用本地方法:
    
            public final native boolean compareAndSetInt(Object o, long offset,
                                                     int expected,
                                                     int x);
    
    
    CAS机制当中使用了3个基本操作数:
    
        内存地址V(主内存中存放的V值,所有线程共享)
    
        旧的预期值A(线程上次从内存中读取的V值A存放在线程的帧栈中,每个线程私有)
    
        要修改的新值B(需要写入内存中并改写V值的B值)
    
    
    更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,
    才会将内存地址V对应的值修改为B(防止多线程并发问题)。
    

    CAS的缺点:

    1.自旋时间过长
    
      使用CAS时非阻塞同步,也就是说不会将线程挂起,会自旋(无非就是一个死循环)进行下一次尝试,如果这里自旋时间过长对性能是很大的消耗。
    
      在并发量比较高的情况下,如果许多线程反复尝试更新某一个变量,却又一直更新不成功,循环往复,会给CPU带来很大的压力。
    
    2.不能保证代码块的原子性
    
      CAS机制所保证的只是一个变量的原子性操作,而不能保证整个代码块的原子性。
      比如需要保证3个变量共同进行原子性的更新,就不得不使用Synchronized了。
    
    3.ABA问题(解决:AtomicStampedReference)
    
      CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,
      但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。
    
      ABA问题的解决思路就是使用版本号:
        在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A。
    
    
    public class AtomicStampedReference<V> {
    
        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);
            }
        }
    
        private volatile Pair<V> pair;
    
        public AtomicStampedReference(V initialRef, int initialStamp) {
            pair = Pair.of(initialRef, initialStamp);
        }
    
        public V getReference() {
            return pair.reference;
        }
    
        public int getStamp() {
            return pair.stamp;
        }
    }
    

    基于CAS实现的原子操作基本类型与数组类型

    java.util.concurrent.atomic:该包中提供了许多基于CAS实现的原子操作类
    
    
    AtomicBoolean,AtomicInteger,AtomicLong等
    
        incrementAndGet() :
            以原子的方式将实例中的原值进行加1操作,并返回最终相加后的结果;
    
        getAndIncrement():
            以原子的方式将实例中的原值加1,返回的是自增前的旧值;
    
        getAndSet(int newValue):
            将实例中的值更新为新值,并返回旧值;
    
       addAndGet(int delta) :
            以原子方式将输入的数值与实例中原本的值相加,并返回最后的结果;
    
    
    AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray(原子更新引用类型数组中的元素)
    
        addAndGet(int i, int delta):
            以原子更新的方式将数组中索引为i的元素与输入值相加;
    
        getAndIncrement(int i):
            以原子更新的方式将数组中索引为i的元素自增加1;
    
        compareAndSet(int i, int expect, int update):
            将数组中索引为i的位置的元素进行更新
    

    原子引用(AtomicReference:提供了引用变量的读写原子性操作)

    原理:CAS + Unsafe
    
    赋值操作不是线程安全的。若想不用锁来实现,可以用AtomicReference<V>这个类,实现对象引用的原子更新。
    
    AtomicReference则对应普通的对象引用,底层使用的是compareAndSwapObject实现CAS:
        比较的是两个对象的地址是否相等(线程在操作value时不会被中断)
    
        private static AtomicReference<User> reference = new AtomicReference<>();
        User user1 = new User("a", 1);
        reference.set(user1);
        User user2 = new User("b",2);
        User user = reference.getAndSet(user2);
        System.out.println(user);    //a,1
        System.out.println(reference.get());    //b,2
    
    
    public class AtomicReference<V>  implements java.io.Serializable {
        private static final long serialVersionUID = -1848883965231344442L;
     
        // 获取Unsafe对象,Unsafe的作用是提供CAS操作
        private static final Unsafe unsafe = Unsafe.getUnsafe();
        private static final long valueOffset;
     
        static {
          try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicReference.class.getDeclaredField("value"));
          } catch (Exception ex) { throw new Error(ex); }
        }
     
        // volatile类型
        private volatile V value;
     
        public final boolean compareAndSet(V expect, V update) {
            return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
        }
    
        public final V getAndSet(V newValue) {
            while (true) {
                V x = get();
                if (compareAndSet(x, newValue))
                    return x;
            }
        }
    }
    

    Java实现自旋锁(非公平锁)

    public class SpinLock {
    
        private AtomicReference<Thread> cas = new AtomicReference<Thread>();
    
        //上锁
        public void lock() {
            Thread current = Thread.currentThread();
            // 利用CAS
            while (!cas.compareAndSet(null, current)) {
                // DO nothing
            }
        }
    
        //解锁
        public void unlock() {
            Thread current = Thread.currentThread();
            cas.compareAndSet(current, null);
        }
    }
    
    优点:
        自旋锁不会使线程状态发生切换,一直处于用户态,即线程一直都是active的;
        不会使线程进入阻塞状态,减少了不必要的上下文切换,执行速度快
    
        非自旋锁在获取不到锁的时候会进入阻塞状态,从而进入内核态,当获取到锁的时候需要从内核态恢复,需要线程上下文切换。
    
  • 相关阅读:
    java23中设计模式之策略模式
    java23中设计模式之模板模式
    java23中设计模式之备忘录模式
    java23中设计模式之命令模式
    java23中设计模式之中介者模式
    java23中设计模式之迭代器模式
    java23中设计模式只责任链模式
    web service -- jdk spring cxf ajax 开发(2)
    洛谷 P1566 加等式
    洛谷 P1439 【模板】最长公共子序列
  • 原文地址:https://www.cnblogs.com/loveer/p/11502521.html
Copyright © 2020-2023  润新知