zoukankan      html  css  js  c++  java
  • CAS(Compare And Swap)

    之前的文章讲了ReentrantLocksynchronized都是通过锁来保证线程安全的,锁机制存在一些问题,例如:

      ❤ 在多线程的竞争下,加锁、释放锁会导致很多线程的上下文切换和调度,对性能有一定的影响;

      ❤ 一个线程持有锁会导致其他需要此锁的线程挂起(强行在锁的区域将并行变为串行);

      ❤ 使用不当还会导致死锁、饥饿、活锁等;

    也许你会说,也可以用volatile,volatile是轻量级的锁,但是不能保证原子性,所以最后还是会回到锁的机制上来;

    synchronized和ReentrantLock都是独占锁,独占锁是一种悲观锁,会导致其余需要锁的线程挂起,等待持有锁的线程释放锁的资源,并且每次只能有一个线程执行;而另一个更加有效的锁就是乐观锁,乐观锁就是乐观的认为每次操作都没有冲突,如果有,则重试,直到成功为止,乐观锁使用到的机制就是CAS。

    CAS(Compare And Swap)

    CAS:Compare and Swap 即:比较并交换。设计并发计算时常用到的一种技术,java.util.concurrent包的基础建立在CAS之上,没有CAS就没有此包,可见CAS的重要性。

    在Java语言出现之前,并发就已经广泛存在并在服务器领域得到了广泛的应用。所以硬件厂商很早就在芯片中加入了大量支持并发操作的原语,从而使硬件性能得到提升,在Intel中,采用的是cmpxchg指令。

    在Java的发展初期,Java语言是不能利用硬件提供的这些便利来提升系统的性能的,随着Java的发展,JNI(Java Native Interface)的出现,使得Java程序可以越过JVM直接调用本地(本机)上的一些方法,这样不仅使得Java在并发上的手段增多了,同时也提高了Java系统的性能。

    CAS有三个操作数:内存值V,旧的预期值A、要修改的新值B,当且仅当预期值A和内存值V相同时,将内存值修改为B并返回true,否则什么都不做并返回false。

    CAS是通过Unsafe类来实现的,下面看看Unsafe里的方法:

    public final native boolean compareAndSwapObject(Object paramObject1, long paramLong, Object paramObject2, Object paramObject3);
    
    public final native boolean compareAndSwapInt(Object paramObject, long paramLong, int paramInt1, int paramInt2);
    
    public final native boolean compareAndSwapLong(Object paramObject, long paramLong1, long paramLong2, long paramLong3);

     上面这三种方法都是Native本地方法,而且都是原子操作(硬件保证)。第一个参数表示需要更新的对象,第二个long表示的是该对象在内存中的偏移地址,第三个是预期值(即旧值),第四个是新值。

    下面我们通过AtomicInteger来增加对CAS的理解,先来看AtomicInteger的类的变量定义,源码如下:

     1 private static final Unsafe unsafe = Unsafe.getUnsafe();
     2     private static final long valueOffset;
     3 
     4     static {
     5       try {
     6         valueOffset = unsafe.objectFieldOffset
     7             (AtomicInteger.class.getDeclaredField("value"));
     8       } catch (Exception ex) { throw new Error(ex); }
     9     }
    10 
    11     private volatile int value;

    上述代码解释:

      ❤ Unsafe是CAS的核心。

      ❤ valueOffset是变量在内存中的偏移地址,因为Unsafe就是根据偏移地址去获取数据的原值的;

      ❤ value是volatile关键字修饰的,这个非常重要,这保证了变量在线程中的可见性;

    再来看一个方法incrementAndGet(),源码如下:

    public final int incrementAndGet() {
            for (;;) {
                int current = get();
                int next = current + 1;
                if (compareAndSet(current, next))
                    return next;
            }
        }
    
    public final boolean compareAndSet(int expect, int update) {
            return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
        }

    incrementAndGet()该方法每次增加1,并返回增加后的值。相当于++i的功能,但是++i不是原子性的,incrementAndGet()是原子性的。

    从上述代码可以看出,incrementAndGet()方法内是一个死循环,保证了value一定会+1成功并返回,利用unsafe.compareAndSwapInt(this, valueOffset, expect, update)保证了对于value修改的线程安全性。

    CAS的缺点

      1.存在ABA情况。CAS在操作时,需要比较值有没有发生变化,没有变化则更新,如果一个变量由A变成了B,再由B变回了A,那么CAS在检查时,就是发现该变量的值没有发生改变,但是实际上却变化了。从Java1.5开始。JDK在atomic提供了一个AtomicStampedReference类来解决ABA问题这个类的compareAndSet(expectedReference, newReference,expectedStamp, newStamp)方法,会首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子的方式将该引用和该标志的值修改为给定的新值。

      2.循环时间长开销大。自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。

      3.只能保证一个共享变量的原子操作。当对一个共享变量进行操作时,我们可以采用CAS方式来保证变量的线程安全,当有多个变量时,CAS就无法保证操作的原子性了,这个时候就只能用锁或者采用AtomicReference类来保证引用对象之间的原子性,AtomicReference支持把多个变量放到一个对象里进行CAS操作。

    作者:Joe
    努力了的才叫梦想,不努力的就是空想,努力并且坚持下去,毕竟这是我相信的力量
  • 相关阅读:
    EasyNVR内网摄像机接入网关+EasyNVS云端管理平台,组件起一套轻量级类似于企业级萤石云的解决方案
    EasyNVR摄像机无插件直播安装使用错误原因解析
    网络摄像机进行互联网视频直播录像方案的选择,EasyNVS or EasyCloud or EasyGBS?
    EasyNVR对接EasyCloud视频云平台进行云端录像
    RTSP安防摄像机(海康大华宇视等)如何推送到RTMP流媒体服务器进行直播
    普通摄像机也能做互联网HLS(m3u8)、RTMP、HTTP-FLV直播?是的,采用基于GBT28181协议的EasyGBS流媒体服务
    对EasyDarwin开源项目2018的思考与2019发展的规划:继续站在巨人的肩膀引入更多巨人
    EasyNVR智能云终端接入AI视频智能分析功能,用户可自定义接入自己的分析算法
    如何自己实现一套EasyNVR这样的无插件流媒体服务器
    宇视4G设备采用GB/T28181协议成功接入EasyGBS国标流媒体平台的设置流程
  • 原文地址:https://www.cnblogs.com/Joe-Go/p/9771452.html
Copyright © 2011-2022 走看看