xiaobenchi

导航

Java并发包中原子操作类原理剖析

Java并发包中原子操作类原理剖析

JUC包提供了一系列的原子性操作类,这些类都是使用非阻塞算法CAS实现的,相比使用锁实现原子性操作这在性能上有很大提高。

1. 原子变量操作类

JUC并发包中包含有AtomicIntegerAtomicLongAtomicBoolean等原子性操作类,它们的原理类似,本章讲解AtomicLong类。AtomicLong是原子性递增或者递减类,其内部使用Unsafe来实现,我们看下面的代码。

public class AutomicLong extends Number implements java.io.Serilizable{
    private static final long serialVersionUID = 1927816293512124184L;
    
    //1. 获取Unsafe实例
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    
    //2. 存放变量value的偏移量
    private static final long valueOffset;
    
    //3. 判断JVM是否支持Long类型无锁CAS
    static final boolean VM_SUPPORTS_LONG_CAS = VMSupportsCS8();
    private static native boolean VMSupportsCS8();
    
    static{
        try{
            //4. 获取value在AtomicLong中的偏移量
            vlaueOffset = unsafe.objectFieldOffset
                (AtomicLong.class.getDeclaredField("value"));
        }catch(Exception ex){throw new Error(ex);}
    }
    //5. 实际变量值
    private volatile long value;
    
    public AtomicLong(long initialValue){
        value = initialValue;
    }
    ...
}
  • 递增和递减操作代码

    //6. 调用unsafe方法,原子性设置value值为原始值+1,返回值为递增后的值
    public final long incrementAndGet(){
       return unsafe.getAndAddLong(this,valueOffset,1L)+1L;
    }
    
    //7. 调用unsafe方法,原子性设置value值为原始值-1,返回值为递减后的值
    public final long decrementAndGet(){
       return unsafe.getAndAddLong(this,valueOffset,-1L)-1L;
    }
    
    //8. 调用unsafe方法,原子性设置value值为原始值+1,返回值原始值
    public final long getAndIncrement(){
       return unsafe.getAndAddLong(this,valueOffset,1L);
    }
    
    //7. 调用unsafe方法,原子性设置value值为原始值-1,返回原始值
    public final long getAndDecrement(){
       return unsafe.getAndAddLong(this,valueOffset,-1L)-1L;
    }
    

    在如上代码内部都是通过调用Unsafe的getAndAddLong方法来实现操作,这个函数是个原子性操作,这里第一个参数是AtomicLong实例的引用,第二个参数是value变量在AtomicLong中的偏移值,第三个参数是要设置的第二个变量的值。

    • getAndIncrement方法在JDK 7中的实现逻辑

      image-20220727165528845

    • getAndIncrement方法在JDK 8中的实现逻辑

      image-20220727165623185

      image-20220727165915705

  • boolean compareAndSet(long expect, long update)方法

    public final boolean compareAndSet(long except, long update){
        return unsafe.compareAndSwapLong(this, valueOffset,expect,update);
    }
    

2. JDK 8 新增的原子操作类LongAdder

  • LongAdder简单介绍

    AtomicLong通过CAS提供了非阻塞的原子性操作,相比使用阻塞算法的同步器来说它的性能已经很好了。使用AtomicLong时,在高并发下大量线程会同时去竞争更新同一个原子变量,但是由于同时只有一个线程的CAS操作会成功,这就造成了大量线程竞争失败后,会通过无限循环不断进行自旋尝试CAS的操作,而这会白白浪费CPU资源。
    因此JDK 8新增了一个原子性递增或者递减类LongAdder用来克服在高并发下使用AtomicLong的缺点。既然AtomicLong的性能瓶颈是由于过多线程同时去竞争一个变量的更新而产生的,那么如果把一个变量分解为多个变量,让同样多的线程去竞争多个资源,是不是就解决了性能问题?是的,LongAdder就是这个思路。

    使用LongAdder时,则是在内部维护多个Cell变量,每个Cell里面有一个初始值为0的long型变量,这样,在同等并发量的情况下,争夺单个变量更新操作的线程量会减少,这变相地减少了争夺共享资源的并发量。另外,多个线程在争夺同一个Cell原子变量时如果失败了,它并不是在当前Cell变量上一直自旋CAS重试,而是尝试在其他Cell的变量上进行CAS尝试,这个改变增加了当前线程重试CAS成功的可能性。最后,在获取LongAdder当前值时,是把所有Cell变量的value值累加后再加上base返回的。

  • LongAdder代码分析

    为了解决高并发下多线程对一个变量CAS争夺失败后进行自旋而造成的降低并发性能问题,LongAdder在内部维护多个Cell元素(一个动态的Cell数组)来分担对单个变量进行争夺的开销。

    • cell构造

      @sun.misc.Contended static final class Cell{
          volatile long value;
          Cell(long x){value = x;}
          final boolean cas(long cmp,long val){
              return UNSAFE.compareAndSwapLong
                  (this,valueOffset,cmp,val);
          }
      }
      

      image-20220730123027733

    • long sum()返回当前的值,内部操作是累加所有Cell内部的value值后再累加base。

      public long sum(){
          Cell[] as = cells; Cell a;
          long sum = base;
          if(as != null){
              for(int i = 0; i < as.length; ++i){
                  if((a = as[i]) != null){
                      sum += a.value;
                  }
              }
              return sum;
          }
      }
      
    • void reset() 为重置操作

      public void reset(){
          Cell[] as = cells; Cell a;
          base = 0L;
          if(as != null){
              for(int i = 0; i < as.length; ++i){
                  if((a = as[i]) != null){
                      a.value = 0L;
                  }
              }
          }
      }
      
    • long sumThenReset()是sum的改造版本。累加之后有重置操作

      public long sumThenReset(){
          Cell[] as = cells; Cell a;
          long sum = base;
          if(as != null){
              for(int i = 0; i < as.length; ++i){
                  if((a = as[i]) != null){
                      sum += a.value;
                      a.value = 0L;
                  }
              }
              return sum;
          }
      }
      
    • long longValue( )等价于sum( )

    • longAccumulate, cells数值被初始化和扩容的地方

  • 小结

    本节介绍了JDK 8中新增的LongAdder原子性操作类,该类通过内部cells数组分担了高并发下多线程同时对一个原子变量进行更新时的竞争量,让多个线程可以同时对cells数组里面的元素进行并行的更新操作。另外,数组元素Cell使用@sun.misc.Contended注解进行修饰,这避免了cells数组内多个原子变量被放入同一个缓存行,也就是避免了伪共享,这对性能也是一个提升。

3. LongAccumlator类原理探究

LongAdder类是LongAccumulator的一个特例,LongAccumulator比LongAdder的功能更强大。

4. 总结

本章介绍了并发包中的原子性操作类,这些类都是使用非阻塞算法CAS实现的,这相比使用锁实现原子性操作在性能上有很大提高。首先讲解了最简单的AtomicLong类的实现原理,然后讲解了JDK 8中新增的LongAdder类和LongAccumulator类的原理。学习完本章后,希望读者在实际项目环境中能因地制宜地使用原子性操作类来提升系统性能。

posted on 2022-07-30 12:48  小迟在努力  阅读(69)  评论(0)    收藏  举报