美文网首页
CAS的一些研究与个人思考

CAS的一些研究与个人思考

作者: 多一根头发灬 | 来源:发表于2019-05-03 15:10 被阅读0次

cas技术,或者说设计思想,其实挺让人着迷的,至少对于我来说是如此,这些天一直在研究这一块,今天写写针对cas的总结,会持续更新;

cas(compare and swap),中文翻译比较替换,大家又将它谈为乐观锁,说到锁,关于java里面太多了,而且还是面试官特别喜欢问的,简直让人头皮发麻,和乐观锁比较最多的就是悲观锁(synchronized)了。除此之外还有偏向锁,轻量级锁,重量级锁,自旋锁,类锁,数据库里面还有表锁,行锁,elasticsearch里面还有全局锁,文档锁,树锁,redis中的分布式锁等等;

我们平时应用多线程技术非常多,比如网上商店卖一类商品,多个用户同时去购买,列车卖票,多个用户同时购买同一车次的车票等等场景,这些都涉及到了一个问题,共享资源,在多个线程去访问共享资源的时候,如何去保证共享资源的一致性?在操作系统里面有一个临界区的概念,其实就是一个程序片段,这个片段在同一时刻,针对共享资源,只能一个线程去访问,这就是锁。那么这个程序片段。对应java里面是什么呢?是synchronized,被synchronized修饰过的程序片段,相当于加了锁,而这个锁,就是悲观锁。那么问题来了,如果一个系统中的共享资源是改多读少的情况,加锁为了保证数据的一致性无可厚非,但是如果这个系统中的共享资源是读多写少的情况呢?要知道,无论是读,还是写,进入该程序片段,都是要加锁的,而每加锁一次这涉及到了系统调用内核态和用户态之间的切换,频繁加锁,释放锁,这个代价是非常大的,所以我们提出了乐观锁的概念,乐观锁在应用层,其实是没有锁的。这也是CAS技术为什么多用于读多写少的应用场景。

CAS是怎么做到没有加锁就能保证数据的一致性的呢?

首先,CAS有三个概念,分别是V,A,B;V表示要修改的值,即共享资源,这个值可以是内存中,可以是数据库中,总之这个是你的共享资源,A表示更新的比较预期值,B表示即将要更新的新值。这么说可能有点不太理解,那么说说cas的操作流程就明白了。流程:V为共享资源,多个线程要去操作V,每个线程会先读取V的值,将值赋给A,其中一个线程要执行更新操作,这个时候,会将这个线程的A值和现在内存中的V值对比,如果没有变化,说明这个之前没有别的线程修改(这里不涉及ABA问题,ABA问题可以通过时间戳或者版本解决),于是将V的值更新为B,如果A值和V值不相等,则不更新或者重新获取A值。

这就是CAS的设计,不过,看到这里,大家有没有想过,如果有两个或者多个线程同时都经过了第一次的V和A值比较呢?那么到了更新一步是不是会出现数据不一致的情况?因为比较和更新其实并不是原子操作。

我们来讨论一下JAVA 8中是怎么解决这个问题的。

比较典型的原子操作对象,AtomicInteger,我们可以看一下里面的源码,自增1是怎么实现的

public final int incrementAndGet() {

    for (;;) {

        int current = get();

        int next = current + 1;

        if (compareAndSet(current, next))

            return next;

    }

}

可以看到,首先get()从内存中获取内存值,在使用之前,你要自己声明一个volatile的所有线程可见变量。接着,加1赋值给了next,接着将current和next传给了compareAndSet函数,如果成功,则返回next值,这个时候,current和next就是分别对应上文的A,B。继续看compareAndSet是怎么保证原子操作的。

public final boolean compareAndSet(int expect, int update) { 

    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);

    }

可以看到,这里直接把值传给了unsafe的compareAndSwapInt方法,由它来保证原子操作,这里我就不贴源码分析了。我直接总结,这个unsafe其实是java中的jni(java native interface)技术调用另外语言的实现,这里是*.cpp,名字太长,我用*代替,这个操作呢其实是硬件层面的技术,涉及到底层的汇编,总的来说就是进入这个unsafe的函数后,在计算机内存运行中,同一时刻通过总线和缓存的锁定,从而实现了比较和替换,达到原子操作的目的,不得不说,看*.cpp文件是一件掉发的操作,珍爱发型,远离秃头;

说了半天,原来这里面最难的原子操作是在硬件层面呀,那我们有没有可能在应用层就能实现呢?答案自然是有的,这里我提供一个我自己的个人想法;

这里我采用的做法是调用已经写好的AtomicInteger来辅助。

1、声明一个针对该共享资源的volatile int变量value,初始化为0;

2、两个或者多个线程进入了第一步的V和A比较,获取value值,这个时候,多个线程就变成了串行化,而不用加锁,因为value是严格排序的;

3、获得value为1的线程执行更新操作,操作完之后,初始化为0,其余不为了1的线程重新获取V值或者处理丢弃;

那么还有一个问题,如果获取到1的线程在执行过程中死掉了怎么办?如果死掉了,那么岂不是这里一直就停住了,不能操作共享资源啦?因为只有value为1的线程才能进入临界区,所以,在这里,我们还要设置一个超时时间,如果超过设定时间,还是要杀掉现在操作资源的线程,重置value值为0和复原在此过程中改变的V值,以便让其他线程进入,当然这种情况几乎很少发生,但还是得考虑。

ABA问题就是线程1的A值去比较V值,发现相等,但是如果在这之前,线程2将V值改了,线程3又将V值改回来,那这样其实A值和V值是相等的,但其实里面已经有了变动,所以叫ABA问题,可以采用时间戳,在对比那一步,加入了时间戳对比就可以了,或者如果是elasticsearch中可以采用版本号比较。

cas应用层实现做法,大家也可以去看看disruptor框架的做法,里面提出了一个前门后门的概念,采用环形缓存做法,据说非常高效;

如果还有别的地方没有考虑到,欢迎大家提出,一起学习进步。

相关文章

  • CAS的一些研究与个人思考

    cas技术,或者说设计思想,其实挺让人着迷的,至少对于我来说是如此,这些天一直在研究这一块,今天写写针对cas的总...

  • 并发编程三:锁

    一、CAS 1.CAS原理 CAS全称为Compare And Swap,比较与交换。CAS是原子性操作的一种实现...

  • CAS与内存屏障: c/c++的内联汇编(S0)

    c++的CAS与内存屏障: c/c++的内联汇编(S0) 多线程编程中偶尔需要接触一些底层的东西,如CAS,原子操...

  • CAS

    CAS:Compare AND Swapjdk1.5之后出现的atomic类都依赖与CAS CAS的三个核心参数 ...

  • 线程安全之无锁

    线程安全策略--CAS CAS与锁 CAS 全称 Compare and Swap ,中文名比较交换空间。JDK5...

  • 每日一悟——哲学是干嘛的?

    所谓哲学是是对一些特定的问题的思考,提出命题,论证,然后在反驳,而哲学的过程是思考,与科学的观察与量化研究不同,是...

  • Java多线程之原子操作类

    本文目录 CAS原理与问题1.1 CAS的操作过程1.2 CAS的问题 Atomic包的使用2.1 原子更新基本类...

  • 研究方向的一些思考

    这次作业有点偷懒,简单叙述一下关于研究方向的一些准备。 最近一直在构思人工智能+人因学的研究方向。周六参...

  • apereo cas客户端登出url重定向

    前面几篇文章都是在讲apereo cas服务端的认证,今天笔者就来说说cas客户端的一些内容。首先是集成cas客户...

  • CRISPR-Cas gRNA设计到底该选哪个工具??

    自从开始分享自己的CRISPR-Cas9学习笔记和记录之后,收到了很多很有用的Tips以及一些未曾思考过的问题。分...

网友评论

      本文标题:CAS的一些研究与个人思考

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