JUC 多线程 CAS 算法

一、什么是 CAS

一句话:比较并交换 == Compare and Swap

解释:一个线程在使用atomicInteger原子变量进行修改值的操作中,底层的CAS算法会拿自己工作空间的值去和主内存空间的值去比较,如果主内存值和期望数值5相同,则去修改为2019,否则修改失败。即CAS有三个操作数:内存值V、旧的预期值A、要修改的值B,当且仅当预期值A和内存值V相同时,将内存值修改为B并返回true,否则什么都不做并返回false。

二、CAS 的底层原理--Unsafe的理解

//相当于i++操作实现
public final int getAndIncrement(){
          return unsafe.getAndInt(this, valueoffset, 1);
}

以atomicInteger为例,所使用的方法都是Unsafe类的方法,也就是CAS算法使用的是Unsafe类提供的方法。

什么是Unsafe:

Unsafe类是CAS的核心类,由于java方法无法访问底层系统,需要本地方法(native)来访问,Unsafe相当于一个后门,基于该类可以直接操作特定的内存的数据,Unsafe存在于sun.misc包中,其内部的方法操作可以像C的指针一样直接操作内存,所以java中CAS操作的执行依赖于 Unsafe类的方法。

注意:Unsafe类中的所有方法都是native修饰的,也就是说Unsafe类中的所有方法都直接调用操作系统底层资源执行相应的任务。

三、CAS算法的缺点

1、循环时间长开销大

如果CAS失败,会一直进行尝试。如果CAS长时间一直不成功,可能会给CPU带来很大的开销。

2、只能保证一个共享变量的原子操作

当对一个共享变量执行操作时,我们只能使用循环CAS的方式来保证原子操作,但是,对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁来保证原子性。

3、会出现ABA问题

四、什么是ABA问题

CAS算法实现一个重要前提需要取出内存中某时刻的数据并在当下时刻比较并替换,那么在这个时间差内会导致数据的变化,也就是说两个线程都读到数据为5,一个线程暂停2秒后,另一个线程把5修改为6然后又修改回5,当第一个线程来到后发现和期望值相同,则修改想要修改的值。

尽管线程的CAS操作成功,但是不代表这个过程就是没问题的。

五、如何解决ABA问题

使用原子引用 + 新增时间戳(修改版本号)

代码演示ABA问题及解决:

/**
 * ABA问题解决
 * @author wannengqingnian
 */
public class TestAtomicStampedReference {

    /**
     * 创建带时间戳的原子引用
     */
    static AtomicStampedReference<Integer> atomicStampedReference
            = new AtomicStampedReference<>(100, 1);

    public static void main(String[] args) {

        //启动一个T1线程模拟ABA问题出现
        new Thread(() -> {
            //获取时间戳
            int stamp = atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName() + "\t第一次时间戳" + stamp);

            //暂停1秒钟T1线程
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            //模拟ABA
            atomicStampedReference.compareAndSet(100, 101,
                    atomicStampedReference.getStamp(),
                    atomicStampedReference.getStamp() + 1);
            System.out.println(Thread.currentThread().getName() + "\t第一次修改版本号 : "+atomicStampedReference.getStamp());
            atomicStampedReference.compareAndSet(101, 100,
                    atomicStampedReference.getStamp(),
                    atomicStampedReference.getStamp() + 1);
            System.out.println(Thread.currentThread().getName() + "\t第二次修改版本号 : "+atomicStampedReference.getStamp());

        }, "T1").start();

        //启动T2线程验证是否解决ABA问题
        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName() + "\t线程获得的版本号 :"+stamp);

            //暂停3秒,确保T1完成ABA问题
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            //开始修改
            Boolean flag = atomicStampedReference.compareAndSet(100, 2019, stamp, stamp + 1);

            System.out.println(Thread.currentThread().getName() + "\t修改是否成功"+flag + "\t此时的版本号" + atomicStampedReference.getStamp());

        }, "T2").start();
    }

}

六、尾巴

关于CAS算法的介绍就是这些,如果有什么疑问或文章有问题,欢迎发消息告知。

本文分享自微信公众号 - JavaArtisan(gh_f521f5243781),作者:万能青年

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-08-20

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • JVM虚拟机垃圾回收机制

    HotSpot JVM把新生区分为三部分:1个Eden区和2个Survivor区,默认内存大小比例为8 : 1 : 1,一般情况下,新创建的对象都会被分配到Ed...

    万能青年
  • JUC 多线程 线程池

    线程池主要是控制运行线程的数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量的线程排队等候,等其他线程执行完毕,再从队列中...

    万能青年
  • JVM 垃圾收集器

    GC算法(引用计数/复制/标清/标整)是内存回收的方法论,垃圾收集器就是算法落地实现。

    万能青年
  • Thread 和 Runnable

    Java 主要是通过 java.lang.Thread 类以及 java.lang.Runnable 接口实现线程机制的。

    希希里之海
  • Facebook副总裁:用户增长诀窍六要决!

    用户1756920
  • 互联网早知道

    1、拼多多第三季度总营收33.72亿元 同比增长697% 股价大涨 距赶超京东市值仅差26亿美元 2、酷派向深圳法院提起诉讼,称锤子科技“欠钱不还” 3、有偿删...

    程序员的酒和故事
  • 50个数据科学应用领域|数据科学

    数据就是资源,如何利用此资源创造商业价值,大家共同研究和实践的问题。数据科学专注于从数据中学习那些有商业价值的东西并加以利用,玩数据的人角色多样,有数据分析师、...

    陆勤_数据人网
  • 巧用 Automator,快速为您的Mac创建自定义右键菜单

     如果你是从 Windows 迁移到 Mac 的用户,你会发现,相比 Windows ,Mac Finder 右键有比较大的差异化,甚至是一些高频需求的缺失,比...

    MAC先森
  • 算法学习笔记(三):冒泡排序和归并排序

    free赖权华
  • 在React应用程序中用RegEx测试密码强度

    尽管一些组织认为应该由用户选择健壮的用户名和密码来保护自己,但是开发人员可以通过将规则包含在程序的设计中来帮助进行良好的密码选择。例如,开发人员可以通过加入进度...

    疯狂的技术宅

扫码关注云+社区

领取腾讯云代金券