美文网首页
浅析AtomicLong以及Unsafe

浅析AtomicLong以及Unsafe

作者: LNAmp | 来源:发表于2016-10-26 16:23 被阅读1290次

    前言

    最近关注着限流、降级相关的设计,开源的Hystrix提供了一种设计思路。限流降级的前提是需要了解系统的各种状态,服务的响应情况,接口的调用情况,数据库的情况等等。其中很重要的一个指标就是qps,那么如何统计qps?Hystrix中有个设计非常好的类HystrixRollingNumber,非常适合用来统计qps。HystrixRollingNumber中利用了LongAdder来提高效率,所以本文先会介绍AtomicLong,UnSafe,下篇文章介绍LongAdder,下下篇文章介绍HystrixRollingNumber...

    AtomicLong简介

    AtomicLong是一个可以原子更新的long值,主要方法有:

    • get()
    • set(long newValue)
    • lazySet(long newValue)
    • getAndSet(long newValue)
    • boolean compareAndSet(long expect, long update)
    • long getAndIncrement()
    • long getAndAdd(long delta)
    • long incrementAndGet()
    • long decrmentAndGet()
    • ...

    其中最值得关注的方法是boolean compareAndSet(long expect, long update)以及long getAndAdd(long delta),前者代表着cas,是底层支持的一种原子更新的方法,cas可用于实现“乐观锁”等。后者的long getAndAdd(long delta)用于给旧值增加delta,并且返回旧值,整个方法是原子的,底层利用了cas。

    AtomicLong实现

    AtomicLong的compareAndSet、getAndAdd等是利用Unsafe的相关功能实现的,贴一段AtomicLong对于Unsafe的使用

    
        // setup to use Unsafe.compareAndSwapLong for updates
        private static final Unsafe unsafe = Unsafe.getUnsafe();
        private static final long valueOffset;
        
        static {
            try {
                valueOffset = unsafe.objectFieldOffset
                    (AtomicLong.class.getDeclaredField("value"));
            } catch (Exception ex) { throw new Error(ex); }
        }
    
        private volatile long value;
    
    

    上述代码中使用Unsafe.getUnsafe()取得了Unsafe对象,通过Unsafe对象的unsafe.objectFieldOffset方法取得了value值在AtomicLong对象中的内存偏移地址(这一点学过c/c++的应该很容易理解)。为了更好地理解AtomicLong实现,下面要插入一段Unsafe的分析。

    Unsafe为啥“unsafe”

    Unsafe为啥叫“unsafe”,是因为它可以直接操作内存地址,直接park/unpark线程,而且sun的每个版本的jdk中对于其实现都可能调整,直接使用非常“不安全”,具体可以参考下这个问题下R大的回答。

    Unsafe对象的获取

    AtomicLong中通过Unsafe.getUnsafe()获取Unsafe对象,代码如下:

       public static Unsafe getUnsafe() {
            Class var0 = Reflection.getCallerClass();
            if(!VM.isSystemDomainLoader(var0.getClassLoader())) {
                throw new SecurityException("Unsafe");
            } else {
                return theUnsafe;
            }
    
    

    从上可以看出会检测调用getUnsafe的类对象的类是不是由BootstrapClassloader加载的,显然我们自己定义的类没法使用
    getUnsafe拿到对象,而是会一直报SecurityException。但是我们还是另辟蹊径:

            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            Unsafe unsafe = (Unsafe) field.get(null);
    

    使用以上反射代码可以成功拿到Unsafe中的static对象theUnsafe。

    Unsafe中的native代码

    Unsafe中存在很多native的方法,主要涉及到直接分配或释放内存、直接获取或者操作对象的字段值,挂起和恢复线程,cas等功能。几种典型的native方法如下:

      public native long allocateMemory(long var1);
      public native void setMemory(Object var1, long var2, long var4, byte var6);
      public native void putChar(long var1, char var3);
      public native char getChar(long var1);
      public native long getLongVolatile(Object var1, long var2);
      public native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
      ...
    

    native方法的实现在c++,java通过JNI的方式调用,从网上找到了c++的实现代码,我们主要看看其中的getLongVolatile和getLong以及cas相关代码的实现,代码可以从这里看到

    jlong
    sun::misc::Unsafe::getLong (jobject obj, jlong offset)
    {
      jlong *addr = (jlong *) ((char *) obj + offset);
      spinlock lock;
      return *addr;
    }
    
    jlong
    sun::misc::Unsafe::getLongVolatile (jobject obj, jlong offset)
    {
      volatile jlong *addr = (jlong *) ((char *) obj + offset);
      spinlock lock;
      return *addr;
    }
    
    static inline bool
    compareAndSwap (volatile jlong *addr, jlong old, jlong new_val)
    {
      jboolean result = false;
      spinlock lock;
      if ((result = (*addr == old)))
        *addr = new_val;
      return result;
    }
    
    

    学过c++对于上述代码应该非常容易看懂,基本都是基于指针操作内存。

    Unsafe中的非native代码

    Unsafe中的非native代码主要包括了getAndSetXXX系列和getAndAddXXX系列,让我看看它的源码

      public final long getAndAddLong(Object var1, long var2, long var4) {
            long var6;
            do {
                var6 = this.getLongVolatile(var1, var2);
            } while(!this.compareAndSwapLong(var1, var2, var6, var6 + var4));
    
            return var6;
        }
        
         public final long getAndSetLong(Object var1, long var2, long var4) {
            long var6;
            do {
                var6 = this.getLongVolatile(var1, var2);
            } while(!this.compareAndSwapLong(var1, var2, var6, var4));
    
            return var6;
        }
    

    基本就是循环+cas的处理方式,这种方式在线程竞争不是很大的情况下还是非常好用的

    AtomicLong对Unsafe的使用

    定位对象中字段内存偏移量

    直接内存操作首要的需要得到对象指针以及偏移地址

      static {
            try {
                valueOffset = unsafe.objectFieldOffset
                    (AtomicLong.class.getDeclaredField("value"));
            } catch (Exception ex) { throw new Error(ex); }
        }
       private static final long valueOffset;
    

    getAndSetXXX以及getAndIncrement

    在知道unsafe的处理方式后,AtomicLong的代码就很简单了

      public final long getAndIncrement() {
            return unsafe.getAndAddLong(this, valueOffset, 1L);
        }
      public final long getAndSet(long newValue) {
            return unsafe.getAndSetLong(this, valueOffset, newValue);
        }
      public final boolean compareAndSet(long expect, long update) {
            return unsafe.compareAndSwapLong(this, valueOffset, expect, update);
        }
    

    总结

    本文简单分析了AtomicLong以及Unsafe的使用,需要注意的是AtomicLong的所有单操作都是原子操作。下文将分析LongAdder并与AtomicLong进行相应的对比。

    相关文章

      网友评论

          本文标题:浅析AtomicLong以及Unsafe

          本文链接:https://www.haomeiwen.com/subject/bpaduttx.html