• CAS总结


    n++的问题不能保证原子操作。 因为被编译后拆分成了3个指令,先获取值,然后加一,然后写回内存。
    把变量声明为volatile,volatile只能保证内存可见性,但是不能保证原子性,在多线程并发下,无法保证线程安全。

    三个参数,一个当前内存值V、旧的预期值A、即将更新的值B,当且仅当预期值A和内存值V相同时,将内存值修改为B并返回true,
    否则什么都不做,并返回false。

    CAS跟synchronized要达到的效果是一样的,保证同一个时刻只有一个线程可以操作成功。
    只不过CAS是硬件级别上面的锁,会锁住总线,保证只有一个CPU可以访问内存,对于用户层面来说应该是乐观锁,synchronized是JVM级别的锁,开销比较大

    分析一下 AtomicInteger,变量value存储的实际值,被volatile修饰,保证其值改变后,其他线程可以立马看见。

    public class AtomicInteger extends Number implements java.io.Serializable {
        // setup to use Unsafe.compareAndSwapInt for updates
        private static final Unsafe unsafe = Unsafe.getUnsafe();
        private static final long valueOffset;
    
        static {
            try {
                valueOffset = unsafe.objectFieldOffset
                    (AtomicInteger.class.getDeclaredField("value"));
            } catch (Exception ex) { throw new Error(ex); }
        }
    
        private volatile int value;
        public final int get() {return value;}
    }
    
    public final int getAndAdd(int delta) {    
        return unsafe.getAndAddInt(this, valueOffset, delta);
    }
    
    // 并发累加操作
    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;
    }

    假设线程A和线程B同时执行getAndAdd操作(分别跑在不同CPU上):

    假设AtomicInteger里面的value原始值为3,即主内存中AtomicInteger的value为3,根据Java内存模型,线程A和线程B各自持有一份value的副本,值为3。
    线程A通过getIntVolatile(var1, var2)拿到value值3,这时线程A被挂起。
    线程B也通过getIntVolatile(var1, var2)方法获取到value值3,运气好,线程B没有被挂起,并执行compareAndSwapInt方法比较内存值也为3,成功修改内存值为2。
    这时线程A恢复,执行compareAndSwapInt方法比较,发现自己手里的值(3)和内存的值(2)不一致,说明该值已经被其它线程提前修改过了,那只能重新来一遍了。
    重新获取value值,因为变量value被volatile修饰,所以其它线程对它的修改,线程A总是能够看到,线程A继续执行compareAndSwapInt进行比较替换,直到成功。

    在这里的一个前提是compareAndSwapInt总是能拿到内存的最新值,然后把自己手上的值跟内存最新值比较,如果相等,即符合预期,则更新,如果不一致,进行下一轮
    循环,重新获取内存最新值。
    底层是通过Unsafe这个类compareAndSwapInt进行比较替换,Unsafe类compareAndSwapInt这个是C++实现的,不同操作系统有不同的实现,它能直接操作的内存偏移地址,
    拿到内存最新值,然后跟旧的值进行比较替换。它能在CPU级别实现锁的机制,保证原子性
    大致原理就是
    1.如果是多处理器,为cmpxchg指令添加lock前缀 ,单处理器,省略lock前缀。
    单处理器实际上不存在多个线程同时并行操作的场景,因为只有一个cpu,操作都是串行的,CPU根据时间片轮询执行不同线程。

    lock前缀可以保证后续执行指令的原子性,老的处理器的做法是锁住总线,开销很大,新的处理器使用缓存锁定。

    参考:https://www.jianshu.com/p/24ffe531e9ee

  • 相关阅读:
    1002. 查找常用字符『简单』
    1108. IP 地址无效化『简单』
    1137. 第 N 个泰波那契数『简单』
    1154. 一年中的第几天『简单』
    1185. 一周中的第几天『简单』
    1207. 独一无二的出现次数『简单』
    暑期集训模拟赛3
    暑期集训模拟赛2
    暑期集训模拟赛1
    CF526F Pudding Monsters 【分治】
  • 原文地址:https://www.cnblogs.com/laowz/p/10089733.html
Copyright © 2020-2023  润新知