前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >浅析AtomicLong以及Unsafe

浅析AtomicLong以及Unsafe

作者头像
LNAmp
发布2018-09-05 15:34:22
5670
发布2018-09-05 15:34:22
举报

前言

最近关注着限流、降级相关的设计,开源的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的使用

代码语言:javascript
复制
    // 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对象,代码如下:

代码语言:javascript
复制
   public static Unsafe getUnsafe() {
        Class var0 = Reflection.getCallerClass();
        if(!VM.isSystemDomainLoader(var0.getClassLoader())) {
            throw new SecurityException("Unsafe");
        } else {
            return theUnsafe;
        }

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

代码语言:javascript
复制
        Field field = Unsafe.class.getDeclaredField("theUnsafe");
        field.setAccessible(true);
        Unsafe unsafe = (Unsafe) field.get(null);

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

Unsafe中的native代码

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

代码语言:javascript
复制
  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相关代码的实现,代码可以从这里看到

代码语言:javascript
复制
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系列,让我看看它的源码

代码语言:javascript
复制
  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的使用

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

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

代码语言:javascript
复制
  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的代码就很简单了

代码语言:javascript
复制
  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进行相应的对比。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2016.10.26 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • AtomicLong简介
  • AtomicLong实现
  • Unsafe为啥“unsafe”
    • Unsafe对象的获取
      • Unsafe中的native代码
        • Unsafe中的非native代码
        • AtomicLong对Unsafe的使用
          • 定位对象中字段内存偏移量
            • getAndSetXXX以及getAndIncrement
            • 总结
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档