zoukankan      html  css  js  c++  java
  • 多线程-CAS操作你真的了解吗

    CAS操作号称无锁优化,也叫作自旋;对于一些常见的操作需要加锁,然后jdk就提供了一些以Atomic开头的类,这些类内部自动带了锁,当然这里的锁并非是用synchronized来实现的,而是通过CAS操作来实现的;

    一、下面是 AtomicInteger 的使用:

    package com.designmodal.design.juc01;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.atomic.AtomicInteger;
    
    /**
     * @author D-L
     * @Classname T03_AtomicInteger
     * @Version 1.0
     * @Description  使用 AtomicInteger 类解决常见的 多线程count++
     *               其内部使用了CAS操作来保证原子性  但是不能保证多个方法连续调用都是原子性
     * @Date 2020/7/21 0:35
     */
    public class T03_AtomicInteger {
        //使用AtomicInteger类
        AtomicInteger count = new AtomicInteger(0);
    
        public void m(){
            for (int i = 0; i < 10000; i++) {
                //等同于 在 count++ 上加锁
                count.incrementAndGet();
            }
        }
    
        public static void main(String[] args) {
            T03_AtomicInteger t = new T03_AtomicInteger();
            List<Thread> threads = new ArrayList<>();
            for (int i = 0; i < 10; i++) {
                threads.add(new Thread(t::m , "Thread" + i));
            }
            threads.forEach((o) -> o.start());
            threads.forEach(o ->{
                try {
                    o.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
    
            System.out.println(t.count);
        }
    }

    二、当然达到使用的级别很简单,看一下API就好了,通过上面的小程序,下面主要来聊一聊原理:

    1、通过源码分析AtomicInteger

    • 首先小程序中定义了一个 AtomicInteger 类型的变量count;
     AtomicInteger count = new AtomicInteger(0);
     public void add(){
    count.incrementAndGet();
    }
    • 调用了AtomicInteger类中incrementAndGet();

    /**
         * Atomically increments by one the current value.
         *
         * @return the updated value
         */
        public final int incrementAndGet() {
            return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
        }
    • 调用unsafe类中的 getAndAddInt(Object var1, long var2, int var4)方法;
    public native int getIntVolatile(Object var1, long var2);
    public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
    
    
     public final int getAndAddInt(Object var1, long var2, int var4) {
            int var5;
            do {
                var5 = this.getIntVolatile(var1, var2);
            } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
    
            return var5;
        }

     这里通过以上三步的操作,最终会进入Unsafe类这里调用的 compareAndSwapInt 意思就是比较然后交换,通过一个while循环,在这里转呀转,直到修改成功;

    CAS(compareAndSwap)(比较并交换):原来想改变的值为0 ,现在想修改成1 ,这里想做到线程安全就必须要加synchronized,现在想用另外一种方式来替换加锁的方法,就是所谓的CAS操作;你可以把它想象成拥有三个参数的方法cas(V ,  Expected  ,  NewValue);  第一个参数V是你要改的那个值,Expected第二个参数是你期望当前的值是多少(也就是如果没有线程修改的时,这个值应该是多少,如果不是期望值那就证明有别的线程修改过),NewValue是要设置的新值;

    上图简单模拟了CAS操作的过程,当线程1和线程2同时读取了共享变量count = 0;在线程1修改的过程中,线程2已经将count值修改为1,那么在线程1修改的时候发现Expected值和V已经匹配不上了,证明已经有线程快我一步将count值改了(可能这里并发量大的时候已经有n多个线程已经修改过了),怎么办呢?那我只能将我的期望值修改成V的值、newValue 在这基础上加1,然后继续在这自旋操作,直到修改成功,这就是自旋操作;

     2、Unsafe类(java并发包底层实现的核心)

    CAS操作不需要加锁是如何做到的呢?原因就在于Unsafe这个类,这个类除了你使用反射之外,你是不能够直接使用的,这里不能使用的原因和ClassLoader有关系,所有AtomicXXX 类内部都是CompareAndSwap / CompareAndSet(新版jdk),这个类中存在好多native方法;

    Unsafe类使Java拥有了像C语言的指针一样操作内存空间的能力,一旦能够直接操作内存,这也就意味着(1)不受JVM管理,也就意味着无法被GC,需要我们手动GC,稍有不慎就会出现内存泄漏。(2)Unsafe的不少方法中必须提供原始地址(内存地址)和被替换对象的地址,偏移量要自己计算,一旦出现问题就是JVM崩溃级别的异常,会导致整个JVM实例崩溃,表现为应用程序直接crash掉。(3)直接操作内存,也意味着其速度更快,在高并发的条件之下能够很好地提高效率。

    CAS:CompareAndSwap,内存偏移地址(var2),预期值(var4),新值(var5)。如果变量在当前时刻的值和预期值expected相等,尝试将变量的值更新为新值(var5)。如果更新成功,返回true;否则,返回false。

     /**
         *  CAS操作 :Unsafe类中的本地方法  由于Java语言无法访问操作系统底层信息(比如:底层硬件设备等),
         *  这时候就需要借助C C++语言来完成了
         * @param var1 对象
         * @param var2 偏移量
         * @param var4 期望值
         * @param var5 新值
         * @return 修改成功返回true 失败返回false
         */
        public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
    
        public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
    
        public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);

    三、CAS操作带来的ABA问题

    ABA问题说白了就是在线程进行CAS操作过程中有多个线程对这个共享变量进行修改,有加有减,兜兜转转又回到起始值,这时该线程浑然不知;打个不恰当的比喻:这个过程就好像你前女友跟你分手以后,在时隔一年之后又找你复合来了,说兜兜转转还是觉得你好,在此期间你前女友已经换了几个男朋友你却浑然不知,那个好看的她穿着你喜欢的小短裙,扎着清纯的马尾辫又回来,好了言归正传,意思就是结果是你期望的,可是这个值是经过很多版本的。

    下面简单模拟ABA操作图:

     

     如何解决ABA问题呢?

    如果是int类型,最终的值也是你期望的,真的是没有所谓,你也不用去纠结这问题;如果你确实就想管一管,那就加一个版本号,做一次修改操作加一,比较检查时连带版本号一起检查。

    基础类型:没有必要管,对你真的没有所谓;

    引用类型:就像是你女朋友和你分手之后又复合,中间经历了多少个男朋友,这个是有所谓的,这时可以通过加版本号来解决;

  • 相关阅读:
    关于相对定位与绝对定位
    一些常用但不平凡的CSS属性
    Java-认识变量、注释并能及时发现错误
    了解Java并学会创建Java项目(一个菜鸟的成长历程)
    竞态条件
    web服务器原理
    信号
    静态网页与动态网页区别
    mmap
    HTTP协议
  • 原文地址:https://www.cnblogs.com/dongl961230/p/13352744.html
Copyright © 2011-2022 走看看