• 并发编程(五):CAS


        在atomic包中,大多数类都是借助unsafe类来实现的,如以下代码

        public static AtomicInteger count = new AtomicInteger(0);
    
        private  static void add() {
            count.incrementAndGet();
        }

      incrementAndGet()方法的实现如下:

        public final int incrementAndGet() {
            return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
        }

    我们再继续深入getAndInt()方法,实现如下:

        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;
        }

      在以上代码中我们着重要说的是 compareAndSwapInt(var1, var2, var5, var5 + var4) 这个方法,compareAndSwap,取每个单词首写字母,就是我们经常说的cas。这个方法中有四个参数var1为当前对象,即代码中的count,var2为当前值,如想计算2加上1等于3的操作,var2即为2,var4为增加量,也就是这个例子中的1,var5为调用底层方法得到的底层当前的值,如果没有其他线程改变底层当前值,返回为2,compareAndSwapInt方法的作用为,如果var2(当前值)与var5(底层当前值相等)则将底层值覆盖为底层当前值(var5)+增加量(var4),否则(其他线程更改了底层当前值,var5不等于var2),重新从底层方法取一次var5的值,如此时var5=4,并重新从var1(当前对象)取一次var2的值,如此时var2的值也变为4,则采取相加操作覆盖底层值,如果var2与var5仍不等,则继续循环取值,直到相等为止。 

      总结一下,CAS操作包含三个操作数,内存位置(V),预期原值(A)和新值(B),如果内存位置的值与预期原值相匹配,处理器会自动将该位置值更新为新值,否则处理器不做任何操作。CAS指令一般都返回该位置的值(有特殊情况只返回是否成功),CAS简而言之就是,我认为位置V应该包含值A,如果包含该值,就将B放到这个位置,否则不更改该位置的值,只告诉我这个位置现在的值即可 。

      对于getIntVolatile方法和compareAndSwapInt的实现如下:

    public native int getIntVolatile(Object var1, long var2);
    public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

     可将两个方法都由native修饰,native修饰的方法为底层方法,一般由c语言来实现。

      锁分为悲观锁和乐观锁,独占锁是一种悲观锁,synchronized是一种独占锁,如果锁被占用,其他需要锁的线程就会被挂起,直到持有锁的线程释放锁,它会产生的问题如下:

      a、在多线程竞争下,加锁释放锁会导致比较多的上下文切换和调度延时,引起性能问题

      b、如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能风险

      另一种就是乐观锁,它每次执行时并不加锁而是假设没有冲突的去完成操作,如果因为冲突失败就重试,直到成功为止,而客观锁用到的机制就是CAS。虽然CAS可以很高效的解决原子操作,但是CAS仍然存在三大问题:

      a、ABA问题,如果一个值原来是A,后被改成B,之后又改为A,那么使用CAS进行检查时发现它的值并没有发生变化,但是实际上却发生变化了,ABA问题的解决思路就是使用版本号,在变量面前加上版本号,每次变量更新的时候把版本加一,上面博客中我们提到atomic包中有专门的类来解决ABA问题

      b、循环时间长开销大。CAS循环如果长时间不成功,会给CPU带来非常大的执行开销。如果JVM能支持处理器提供的pause指令则效率会有一定的提升,pause指令有两个作用,推迟流水线执行,使cpu不会消耗过多的执行资源和避免退出循环的时候因内存顺序冲突而引起CPU流水线被清空,提高CPU执行效率

      c、只能保证一个共享变量的原子操作。当对一个共享变量执行操作时,我们可以使用循环CAS来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候可以用锁或把多个共享变量合并成一个共享变量来操作或者把多个变量放在一个对象里(atomic包中有对引用类型的原子操作)来进行CAS操作

  • 相关阅读:
    联赛前第五阶段总结
    陶陶摘苹果 —— 线段树维护单调栈
    联赛前第三阶段总结
    联赛前第四阶段总结
    [NOIP
    超级跳马 —— 矩阵快速幂优化DP
    我的博客园美化
    Wedding —— 2-SAT
    C++运算符优先级
    water——小根堆+BFS
  • 原文地址:https://www.cnblogs.com/sbrn/p/8999709.html
Copyright © 2020-2023  润新知