浅析AtomicLong以及Unsafe

前言

最近关注着限流、降级相关的设计,开源的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进行相应的对比。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏飞扬的花生

在ASP.MVC中使用Ajax

      Asp.net MVC 抛弃了Asp.net WebForm那种高度封装的控件,让我们跟底层的HTML有了更多的亲近。可以更自由、更灵活的去控制HT...

22690
来自专栏技术博客

MVC Html.RenderPartial和Html.partial

①Html.Partial是HtmlHelper的扩展方法,而Html.RenderPartial是HtmlHelper自带方法,两者功能相近。但

18640
来自专栏Android 开发学习

JsBridge 源码分析

19230
来自专栏落影的专栏

使用AudioToolbox播放AAC

前言 使用VideoToolbox硬编码H.264 使用VideoToolbox硬解码H.264 使用AudioToolbox编码AAC 在上一篇中,介绍...

43540
来自专栏java一日一条

50个常见的 Java 错误及避免方法(第三部分)

当我们尝试调用带有错误参数的Java代码时,通常会产生此Java错误消息(@ghacksnews):

15930
来自专栏Jed的技术阶梯

spark读写HBase之使用hortonworks的开源框架shc(二):入门案例

shc测试环境的搭建参考: spark读写HBase之使用hortonworks的开源框架shc(一):源码编译以及测试工程创建

28430
来自专栏编码小白

tomcat请求处理分析(一) 启动container实例

1.1.1  启动container实例 其主要是进行了生命周期中一系列的操作之后调用StandardEngine中的 startInternal方法,不难看出...

38760
来自专栏ascii0x03的安全笔记

C/C++网络编程时注意的问题小结

1.网络编程在自己定义结构体实现协议的时候,一定要注意字节对齐这个问题。否则sizeof和强制转换指针的时候都会出现很难发现的bug。 什么是字节对齐自行百度。...

36390
来自专栏大内老A

WCF的Binding模型之四:信道工厂(Channel Factory)

由于信道管理器在客户端和服务端所起的不同作用,分为信道监听器和信道工厂。和服务端的信道监听其相比,处于客户端的信道工厂显得简单。从名称就可以看得出来,信道工厂的...

18680
来自专栏c#开发者

MSMQ突破4M限制的方法

    在默认情况下msmq 3.0(windows xp ,windows 2003)最大单个消息(Message size)大小4M;(包括正文和全部指定属...

36540

扫码关注云+社区

领取腾讯云代金券