Java 9 變量句柄-VarHandle

Java 9的發(fā)布的新特性除了最主要的模塊化之外浅侨,在API方面也為開發(fā)者們帶來了很多有用的特性技俐,本篇我們來探討一下java 9提供的新的API-VarHandle 對 memory order 的支持蕴侣,及其在JUC同步類中的應用涡驮。在開始本篇之前固耘,你需要對JMM(Java 內存模型)有一定的認知眷射。

VarHandle 的必要性

隨著Java中的并發(fā)和并行編程的不斷擴大,我們經常會需要對某個類的字段進行原子或有序操作雕沿,但是 JVM 對Java開發(fā)者所開放的權限非常有限。例如:如果要原子性地增加某個字段的值猴仑,到目前為止我們可以使用下面三種方式:

  • 使用AtomicInteger來達到這種效果审轮,這種間接管理方式增加了空間開銷,還會導致額外的并發(fā)問題辽俗;
  • 使用原子性的FieldUpdaters疾渣,由于利用了反射機制,操作開銷也會更大崖飘;
  • 使用sun.misc.Unsafe提供的JVM內置函數(shù)API榴捡,雖然這種方式比較快,但它會損害安全性和可移植性朱浴,當然在實際開發(fā)中也很少會這么做吊圾。

在 VarHandle 出現(xiàn)之前达椰,這些潛在的問題會隨著原子API的不斷擴大而越來越遭。VarHandle 的出現(xiàn)替代了java.util.concurrent.atomicsun.misc.Unsafe的部分操作项乒。并且提供了一系列標準的內存屏障操作啰劲,用于更加細粒度的控制內存排序。在安全性檀何、可用性蝇裤、性能上都要優(yōu)于現(xiàn)有的API。VarHandle 可以與任何字段频鉴、數(shù)組元素或靜態(tài)變量關聯(lián)栓辜,支持在不同訪問模型下對這些類型變量的訪問,包括簡單的 read/write 訪問垛孔,volatile 類型的 read/write 訪問啃憎,和 CAS(compare-and-swap)等。


創(chuàng)建VarHandle

VarHandle通過MethodHandleslookup()方法創(chuàng)建似炎,下面演示了非靜態(tài)變量和數(shù)組的獲取方式:

public class VarhandleFoo {
    
    private Point[] points;

    private static final VarHandle QA;//for arrays
    private static final VarHandle X;//for Variables
    static {
        try {
            QA =  MethodHandles.arrayElementVarHandle(Point[].class);
            X = MethodHandles.lookup().
                    findVarHandle(Point.class, "x", int.class); //or
            //X = MethodHandles.lookup().in(Point.class).findVarHandle(Point.class, "x", int.class);
        } catch (ReflectiveOperationException e) {
            throw new Error(e);
        }
    }

    class Point {
        volatile int x;
        // ...
    }
}

Lookup

LookupMethodHandles的內部類辛萍,位于java.lang.invoke包中,它是一個用于創(chuàng)建方法和變量句柄的工廠羡藐。與我們熟知的核心反射API不同的是贩毕,核心反射API在每一次方法被調用時都會進行訪問檢查,而通過Lookup創(chuàng)建的方法句柄的訪問檢查是在它被創(chuàng)建時進行的仆嗦。通過Lookup提供的工廠方法辉阶,我們可以訪問對象的任何方法、構造函數(shù)和參數(shù)變量瘩扼。由工廠方法創(chuàng)建的每個方法句柄都等同于方法的字節(jié)碼行為(bytecode behavior)谆甜,也就是說,JVM調用方法句柄與執(zhí)行和方法句柄相關的字節(jié)碼行為一致集绰。(有關Lookup的更詳細介紹规辱,請參閱官方API-MethodHandles.Lookup

Lookup在Java 9中對變量訪問也添加了相應的工廠方法,用于生成變量句柄-VarHandle:

  • findVarHandle:用于創(chuàng)建對象中非靜態(tài)字段的VarHandle栽燕。接收參數(shù)有三個罕袋,第一個為接收者的class對象,第二個是字段名稱碍岔,第三個是字段類型浴讯。
  • findStaticVarHandle:用于創(chuàng)建對象中靜態(tài)字段的VarHandle,接收參數(shù)與findVarHandle一致蔼啦。
  • unreflectVarHandle:通過反射字段Field創(chuàng)建VarHandle榆纽。

獲取VarHandle后,接下來就是對變量的訪問,下面列舉了幾種簡單的訪問形式:

//plain read
int x = (int) X.get(this);
Point p = (Point) QA.get(points,10);

//plain write
X.set(this,1);
QA.set(points,10,new Point());

//CAS
X.compareAndSet(this,0,1);
QA.compareAndSet(points,10,p,new Point());

//Numeric Atomic Update
X.getAndAdd(this,10);

VarHandle中的每個方法都被稱為 access mode method奈籽,接收的參數(shù)都是一個協(xié)調表達式饥侵,該表達式精確地指示了要訪問變量的對象,后續(xù)的調用參數(shù)表示當前訪問模式的值唠摹。例如爆捞,CAS方法需要兩個后續(xù)參數(shù):預期值和新值。
需要注意的是勾拉,access mode將覆蓋在變量聲明時指定的任何內存排序效果煮甥。 例如,一個VarHandle使用 get 模式訪問一個字段時藕赞,即使這個字段已經被聲明為volatile成肘,也會把這個字段當做方法指定的訪問模式進行訪問。因此使用時要非常小心斧蜕。

內存屏障

VarHandle除了支持在各種訪問模式下訪問變量之外双霍,還提供了一組內存屏障方法,為內存排序提供更細粒度的控制批销。主要有以下幾個方法:

public static void fullFence() {
    UNSAFE.fullFence();
}
public static void acquireFence() {
    UNSAFE.loadFence();
}
public static void releaseFence() {
    UNSAFE.storeFence();
}
public static void loadLoadFence() {
    UNSAFE.loadLoadFence();
}
public static void storeStoreFence() {
    UNSAFE.storeStoreFence();
}

本質上來看洒闸,這些內存屏障都是通過Unsafe類的fullFenceloadFencestoreFence來實現(xiàn)均芽,關于Unsafe丘逸,大家可以參考我的另外一篇文章:JUC源碼分析—CAS和Unsafe

VarHandle的應用

VarHandle出世后掀宋,JUC中多數(shù)同步類中都使用VarHandle替代了Unsafe深纲,這里我們拿AQS舉例說明,直接看代碼:

// VarHandle mechanics
private static final VarHandle STATE;
private static final VarHandle HEAD;
private static final VarHandle TAIL;

static {
    try {
        MethodHandles.Lookup l = MethodHandles.lookup();
        STATE = l.findVarHandle(AbstractQueuedSynchronizer.class, "state", int.class);
        HEAD = l.findVarHandle(AbstractQueuedSynchronizer.class, "head", Node.class);
        TAIL = l.findVarHandle(AbstractQueuedSynchronizer.class, "tail", Node.class);
    } catch (ReflectiveOperationException e) {
        throw new ExceptionInInitializerError(e);
    }

    // Reduce the risk of rare disastrous classloading in first call to
    // LockSupport.park: https://bugs.openjdk.java.net/browse/JDK-8074773
    Class<?> ensureLoaded = LockSupport.class;
}

/**
 * 初始化queue
 */
private final void initializeSyncQueue() {
    Node h;
    if (HEAD.compareAndSet(this, null, (h = new Node())))
        tail = h;
}

AQS中對變量state, head, tail的訪問都改為了VarHandle方式劲妙。例如湃鹊,如果要用CAS方式修改head節(jié)點,只需要調用VarHandlecompareAndSet即可(HEAD.compareAndSet(this, null, new Node()))镣奋。

目前币呵,在java.util.concurrent包中對變量的訪問基本上都由Unsafe改為了VarHandle,有關VarHandle的更多詳細使用方式唆途,請參考JUC源碼富雅。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市肛搬,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌毕贼,老刑警劉巖温赔,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異鬼癣,居然都是意外死亡陶贼,警方通過查閱死者的電腦和手機啤贩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門拜秧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來痹屹,“玉大人,你說我怎么就攤上這事枉氮≈狙埽” “怎么了楼肪?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵春叫,是天一觀的道長暂殖。 經常有香客問我,道長廉沮,這世上最難降的妖魔是什么颓遏? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮滞时,結果婚禮上叁幢,老公的妹妹穿的比我還像新娘。我一直安慰自己坪稽,他們只是感情好曼玩,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著窒百,像睡著了一般黍判。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上篙梢,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天顷帖,我揣著相機與錄音,去河邊找鬼。 笑死贬墩,一個胖子當著我的面吹牛榴嗅,可吹牛的內容都是我干的。 我是一名探鬼主播陶舞,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼嗽测,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了肿孵?” 一聲冷哼從身側響起唠粥,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎颁井,沒想到半個月后厅贪,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡雅宾,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年养涮,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片眉抬。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡贯吓,死狀恐怖,靈堂內的尸體忽然破棺而出蜀变,到底是詐尸還是另有隱情悄谐,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布库北,位于F島的核電站爬舰,受9級特大地震影響,放射性物質發(fā)生泄漏寒瓦。R本人自食惡果不足惜情屹,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望杂腰。 院中可真熱鬧垃你,春花似錦、人聲如沸喂很。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽少辣。三九已至凌摄,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間漓帅,已是汗流浹背望伦。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工林说, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留煎殷,地道東北人屯伞。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像豪直,于是被迫代替她去往敵國和親劣摇。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344