CAS

欢迎光临我的博客[http://poetize.cn],前端使用Vue2,聊天室使用Vue3,后台使用Spring Boot

乐观锁与悲观锁

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的;
    不会使线程进入阻塞状态,减少了不必要的上下文切换,执行速度快

    非自旋锁在获取不到锁的时候会进入阻塞状态,从而进入内核态,当获取到锁的时候需要从内核态恢复,需要线程上下文切换。
posted @ 2019-09-10 19:53  LittleDonkey  阅读(464)  评论(0编辑  收藏  举报