20-JUC底層工具類Unsafe

Unsafe

java高併發中主要涉及到類位於java.util.concurrent包中,簡稱juc,juc中大部分類都是依賴於Unsafe來實現的,主要用到了Unsafe中的CAS、線程掛起、線程恢復等相關功能

在這裏插入圖片描述

Unsafe是位於sun.misc包下的一個類,主要提供一些用於執行低級別、不安全操作的方法,如直接訪問系統內存資源、自主管理內存資源等,這些方法在提升Java運行效率、增強Java語言底層資源操作能力方面起到了很大的作用

從Unsafe功能圖上看出,Unsafe提供的API大致可分爲內存操作、CAS、Class相關、對象操作、線程調度、系統信息獲取、內存屏障、數組操作等幾類,本文主要介紹3個常用的操作:CAS、線程調度、對象操作。

Unsafe中的CAS操作

/**
 * CAS 操作
 *
 * @param o        包含要修改field的對象
 * @param offset   對象中某field的偏移量
 * @param expected 期望值
 * @param update   更新值
 * @return true | false
 */
public final native boolean compareAndSwapObject(Object o, long offset, Object expected, Object update);

public final native boolean compareAndSwapInt(Object o, long offset, int expected,int update);

public final native boolean compareAndSwapLong(Object o, long offset, long expected, long update);

什麼是CAS? 即比較並替換,實現併發算法時常用到的一種技術。CAS操作包含三個操作數——內存位置、預期原值及新值。執行CAS操作的時候,將內存位置的值與預期原值比較,如果相匹配,那麼處理器會自動將該位置值更新爲新值,否則,處理器不做任何操作,多個線程同時執行cas操作,只有一個會成功。我們都知道,CAS是一條CPU的原子指令(cmpxchg指令),不會造成所謂的數據不一致問題,Unsafe提供的CAS方法(如compareAndSwapXXX)底層實現即爲CPU指令cmpxchg。執行cmpxchg指令的時候,會判斷當前系統是否爲多核系統,如果是就給總線加鎖,只有一個線程會對總線加鎖成功,加鎖成功之後會執行cas操作,也就是說CAS的原子性實際上是CPU實現的, 其實在這一點上還是有排他鎖的,只是比起用synchronized, 這裏的排他時間要短的多, 所以在多線程情況下性能會比較好

說一下offset,offeset爲字段的偏移量,每個對象有個地址,offset是字段相對於對象地址的偏移量,對象地址記爲baseAddress,字段偏移量記爲offeset,那麼字段對應的實際地址就是baseAddress+offeset,所以cas通過對象、偏移量就可以去操作字段對應的值了。

CAS在java.util.concurrent.atomic相關類、Java AQS、JUC中併發集合等實現上有非常廣泛的應用,我們看一下java.util.concurrent.atomic.AtomicInteger類. 這個類可以在多線程環境中對int類型的數據執行高效的原子修改操作,並保證數據的正確性

在這裏插入圖片描述

在這裏插入圖片描述

Unsafe中原子操作相關方法介紹

/**
 * int類型值原子操作,對var2地址對應的值做原子增加操作(增加var4)
 *
 * @param var1 操作的對象
 * @param var2 var2字段內存地址偏移量
 * @param var4 需要加的值
 * @return
 */
public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
        var5 = this.getIntVolatile(var1, var2);
    } while (!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

    return var5;
}

/**
 * long類型值原子操作,對var2地址對應的值做原子增加操作(增加var4)
 *
 * @param var1 操作的對象
 * @param var2 var2字段內存地址偏移量
 * @param var4 需要加的值
 * @return 返回舊值
 */
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;
}

/**
 * int類型值原子操作方法,將var2地址對應的值置爲var4
 *
 * @param var1 操作的對象
 * @param var2 var2字段內存地址偏移量
 * @param var4 新值
 * @return 返回舊值
 */
public final int getAndSetInt(Object var1, long var2, int var4) {
    int var5;
    do {
        var5 = this.getIntVolatile(var1, var2);
    } while (!this.compareAndSwapInt(var1, var2, var5, var4));

    return var5;
}

/**
 * long類型值原子操作方法,將var2地址對應的值置爲var4
 *
 * @param var1 操作的對象
 * @param var2 var2字段內存地址偏移量
 * @param var4 新值
 * @return 返回舊值
 */
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;
}

/**
 * Object類型值原子操作方法,將var2地址對應的值置爲var4
 *
 * @param var1 操作的對象
 * @param var2 var2字段內存地址偏移量
 * @param var4 新值
 * @return 返回舊值
 */
public final Object getAndSetObject(Object var1, long var2, Object var4) {
    Object var5;
    do {
        var5 = this.getObjectVolatile(var1, var2);
    } while (!this.compareAndSwapObject(var1, var2, var5, var4));

    return var5;
}

內部通過自旋的CAS操作實現的,這些方法都可以保證操作的數據在多線程環境中的原子性,正確性

Unsafe中線程調度相關方法

//取消阻塞線程
public native void unpark(Object thread);
//阻塞線程,isAbsolute:是否是絕對時間,如果爲true,time是一個絕對時間,如果爲false,time是一個相對時間,time表示納秒
public native void park(boolean isAbsolute, long time);
//獲得對象鎖(可重入鎖)
@Deprecated
public native void monitorEnter(Object o);
//釋放對象鎖
@Deprecated
public native void monitorExit(Object o);
//嘗試獲取對象鎖
@Deprecated
public native boolean tryMonitorEnter(Object o);

調用park後,線程將被阻塞,直到unpark調用或者超時,如果之前調用過unpark,不會進行阻塞,即park和unpark不區分先後順序。monitorEnter、monitorExit、tryMonitorEnter 3個方法已過期,不建議使用了。

Unsafe中保證變量的可見性

java中操作內存分爲主內存和工作內存,共享數據在主內存中,線程如果需要操作主內存的數據,需要先將主內存的數據複製到線程獨有的工作內存中,操作完成之後再將其刷新到主內存中。如線程A要想看到線程B修改後的數據,需要滿足:線程B修改數據之後,需要將數據從自己的工作內存中刷新到主內存中,並且A需要去主內存中讀取數據。

被關鍵字volatile修飾的數據,有2點語義:

  • 如果一個變量被volatile修飾,讀取這個變量時候,會強制從主內存中讀取,然後將其複製到當前線程的工作內存中使用
  • 給volatile修飾的變量賦值的時候,會強制將賦值的結果從工作內存刷新到主內存

上面2點語義保證了被volatile修飾的數據在多線程中的可見性

Unsafe中提供了和volatile語義一樣的功能的方法

//設置給定對象的int值,使用volatile語義,即設置後立馬更新到內存對其他線程可見
public native void  putIntVolatile(Object o, long offset, int x);
//獲得給定對象的指定偏移量offset的int值,使用volatile語義,總能獲取到最新的int值。
public native int getIntVolatile(Object o, long offset);

putIntVolatile方法,參數:

  • o:表示需要操作的對象
  • offset:表示操作對象中的某個字段地址偏移量
  • x:將offset對應的字段的值修改爲x,並且立即刷新到主存中

調用這個方法,會強制將工作內存中修改的數據刷新到主內存中。

getIntVolatile方法,參數

  • o:表示需要操作的對象
  • offset:表示操作對象中的某個字段地址偏移量

每次調用這個方法都會強制從主內存讀取值,將其複製到工作內存中使用。

第22天:JUC底層工具類Unsafe

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章