淺析AtomicLong以及Unsafe

前言

最近關(guān)注著限流、降級相關(guān)的設(shè)計,開源的Hystrix提供了一種設(shè)計思路。限流降級的前提是需要了解系統(tǒng)的各種狀態(tài),服務(wù)的響應(yīng)情況,接口的調(diào)用情況,數(shù)據(jù)庫的情況等等。其中很重要的一個指標(biāo)就是qps,那么如何統(tǒng)計qps?Hystrix中有個設(shè)計非常好的類HystrixRollingNumber,非常適合用來統(tǒng)計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()
  • ...

其中最值得關(guān)注的方法是boolean compareAndSet(long expect, long update)以及l(fā)ong getAndAdd(long delta),前者代表著cas,是底層支持的一種原子更新的方法,cas可用于實(shí)現(xiàn)“樂觀鎖”等。后者的long getAndAdd(long delta)用于給舊值增加delta,并且返回舊值,整個方法是原子的,底層利用了cas。

AtomicLong實(shí)現(xiàn)

AtomicLong的compareAndSet、getAndAdd等是利用Unsafe的相關(guān)功能實(shí)現(xiàn)的,貼一段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對象中的內(nèi)存偏移地址(這一點(diǎn)學(xué)過c/c++的應(yīng)該很容易理解)。為了更好地理解AtomicLong實(shí)現(xiàn),下面要插入一段Unsafe的分析。

Unsafe為啥“unsafe”

Unsafe為啥叫“unsafe”,是因為它可以直接操作內(nèi)存地址,直接park/unpark線程,而且sun的每個版本的jdk中對于其實(shí)現(xiàn)都可能調(diào)整,直接使用非?!安话踩保唧w可以參考下這個問題下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;
        }

從上可以看出會檢測調(diào)用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的方法,主要涉及到直接分配或釋放內(nèi)存、直接獲取或者操作對象的字段值,掛起和恢復(fù)線程,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方法的實(shí)現(xiàn)在c++,java通過JNI的方式調(diào)用,從網(wǎng)上找到了c++的實(shí)現(xiàn)代碼,我們主要看看其中的getLongVolatile和getLong以及cas相關(guān)代碼的實(shí)現(xiàn),代碼可以從這里看到

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

學(xué)過c++對于上述代碼應(yīng)該非常容易看懂,基本都是基于指針操作內(nèi)存。

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

基本就是循環(huán)+cas的處理方式,這種方式在線程競爭不是很大的情況下還是非常好用的

AtomicLong對Unsafe的使用

定位對象中字段內(nèi)存偏移量

直接內(nèi)存操作首要的需要得到對象指針以及偏移地址

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

總結(jié)

本文簡單分析了AtomicLong以及Unsafe的使用,需要注意的是AtomicLong的所有單操作都是原子操作。下文將分析LongAdder并與AtomicLong進(jìn)行相應(yīng)的對比。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • JDK中的java.util.concurrent.atomic包提供了一系列支持無鎖線程安全修改操作的基礎(chǔ)變量。...
    Justlearn閱讀 1,000評論 0 2
  • Java8張圖 11、字符串不變性 12、equals()方法、hashCode()方法的區(qū)別 13、...
    Miley_MOJIE閱讀 3,902評論 0 11
  • 當(dāng)多個線程同時更新公共變量,會導(dǎo)致線程不安全,通常大家可以會想到使用synchronized關(guān)鍵字或者Lock來解...
    miaoLoveCode閱讀 3,969評論 0 5
  • 從三月份找實(shí)習(xí)到現(xiàn)在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發(fā)崗...
    時芥藍(lán)閱讀 42,818評論 11 349
  • 故事本身 我用了將近一個月的時間慢慢去讀這個故事,期間不斷地產(chǎn)生疑惑,又漸漸地豁然開朗。其實(shí)讀書,每個人看到的終究...
    史妍閱讀 476評論 0 0

友情鏈接更多精彩內(nèi)容