• CAS


    concurrent包下的类都有下面的实现模式
    1、首先,声明变量为volatile
    2、然后,使用CAS的原子条件更新来实现线程之间的同步
    3、配合使用volatile的写可见性和CAS的原子性来实现线程之间的通信(线程安全)

    最近在看 java.util.concurrent.atomic 包下的AtomicInteger源码发现它是利用CAS来实现原子操作、Volatile保证元素的可见性来实现无锁下的线程安全。  决定深入了解一下CAS
    MySQL中的MVCC(多版本并发控制)中的乐观锁也是通过CAS机制和版本号实现无锁更新数据的
    CAS Campare And Swap 比较和交换
    CAS(V,E,N) 的三个参数
    V 要修改的对象(有的说是内存位置)
    E  expect 期望原值
    N  new 要修改为的新值

    流程:
    1、获取要修改的变量的现有值
    2、和期望值比较,是否相同
    3、相同的话则将变量的值修改为新值并返回TRUE,否则什么都不做并返回FALSE。

    AtomicInteger:   
     /**
         * Atomically sets the value to the given updated value
         * if the current value {@code ==} the expected value.
         *
         * @param expect the expected value
         * @param update the new value
         * @return {@code true} if successful. False return indicates that
         * the actual value was not equal to the expected value.
         */
        public final boolean compareAndSet(int expect, int update) {
            return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
        }
    
    
    另外,还有许多CAS操作时自旋的,即如果操作不成功,会一直重试,直到操作成功为止。


    a++的操作是非原子性的,它实际上是两个操作
    1、将a的值加一

    2、将加一后的值赋给a
    在多线程环境下,可能存在下面的问题
    1、在不用volatile修饰的情况下
      线程1获取a的值为1,线程2取a的值为1,分别对其进行a++操作,之后a的b不是3人、而是2
    2、在用volatile修饰的情况下
      线程1获取a的值为1,线程2获取a的值为1
      线程1在对a加一,但未将加一的值赋给a之前,线程2完成了对a的加一并赋值的操作,此时变量a的值为2
      由于volatile修饰的a保证了可见性,此时线程1中的工作内存中的a值无效,重新从堆内存中获取变量a的值,然后完成将a的值赋值为2
      两个线程分别对变量a进行加一操作后,变量a的值不是期望的3,而是2,这是由于加一和赋值的操作不是原子性导致的,所以并发包内提供的原子性的自增方法
    AtomicInteger:   
    /**
         * Atomically increments by one the current value.
         *
         * @return the updated value
         */
        public final int incrementAndGet() {
            return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
        }
    
    Unsafe:
        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的使用是受限的,只有授信的代码才能使用,即JDK库里面的内才可以使用(其帮助Java访问操作系统底层资源,使Java具有底层操作能力,可以提升运行效率)
    private static final Unsafe theUnsafe;

    // 私有化构造函数

    private Unsafe() {
    }
    //
    只有此方法可以获取Unsafe对象
    @CallerSensitive
    public static Unsafe getUnsafe() {
    Class var0 = Reflection.getCallerClass();
    if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
    throw new SecurityException("Unsafe");
    } else {
    return theUnsafe;
    }
    }

    但是可以通过使用反射来获取Unsafe对象

     Class unsafeClass = Unsafe.class;
            try {
                Field f = unsafeClass.getDeclaredField("theUnsafe");
                f.setAccessible(true);
                Unsafe unsafe = (Unsafe) f.get(null);
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }

    Unsafe类的方法

     /**
         * CAS
         * @param var1 要修改field的对象
         * @param var2 对象中某field的偏移量
         * @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);

    获取给定的paramField的内存地址偏移量,这个值对于给定的field是唯一且固定不变的

     public native int arrayBaseOffset(Class<?> var1);


    CAS存在的问题 ABA问题
    ABA:例如两个线程同时更新同一个数据,线程1操作比较快,先把A修改为了B,然后再修改为了A;
    线程2读取到线程1两次修改后的值A和之前的期望值A比较,相同,则认为没有被修改过符合期望,接着修改为新值。
    解决方案是引入版本的概念:如A1-B2-A3,这样,另一个线程读取到的A3和期望值A1虽然值相同但是版本不同,则不进行后续的操作。
    AtomicInteger中的valueOffset就是解决ABA问题的,这个具体为什么,我还没看懂,
    private static final long valueOffset;
    
        static {
            try {
                valueOffset = unsafe.objectFieldOffset
                    (AtomicInteger.class.getDeclaredField("value"));
            } catch (Exception ex) { throw new Error(ex); }
        }
    valueOffset表示的是变量值在内存中的偏移地址,因为Unsafe就是根据内存偏移地址获取数据的原值的

    value是用volatile修饰的,这是非常关键的

    CAS如果保证原子性的?
    CAS包含C和S两个操作,其能保证原子性是因为CAS是由底层CPU支持的原子操作,其原子性是在硬件层面进行保证的。



  • 相关阅读:
    centos8.2安装nginx
    Centos8安装PostgreSQL
    PostgreSQL 安装时提示下载元数据失败
    MySQL8定时备份
    Centos8安装Docker
    Centos8安装中文字体
    Centos8源码安装libgdiplus
    MySQL拖拽排序
    MySQL8修改事务隔离级别
    MySQL启动问题
  • 原文地址:https://www.cnblogs.com/yangyongjie/p/10654084.html
Copyright © 2020-2023  润新知