前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JUC 多线程 CAS 算法

JUC 多线程 CAS 算法

作者头像
万能青年
发布2019-08-30 14:56:09
3790
发布2019-08-30 14:56:09
举报

一、什么是 CAS

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

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

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

代码语言:javascript
复制
//相当于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问题及解决:

代码语言:javascript
复制
/**
 * 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算法的介绍就是这些,如果有什么疑问或文章有问题,欢迎发消息告知。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-08-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 JavaArtisan 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档