溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

通過(guò)重寫(xiě)hashCode()方法將偏向鎖性能提高4倍的方法步驟

發(fā)布時(shí)間:2021-10-12 14:07:18 來(lái)源:億速云 閱讀:207 作者:iii 欄目:編程語(yǔ)言

這篇文章主要介紹“通過(guò)重寫(xiě)hashCode()方法將偏向鎖性能提高4倍的方法步驟”,在日常操作中,相信很多人在通過(guò)重寫(xiě)hashCode()方法將偏向鎖性能提高4倍的方法步驟問(wèn)題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”通過(guò)重寫(xiě)hashCode()方法將偏向鎖性能提高4倍的方法步驟”的疑惑有所幫助!接下來(lái),請(qǐng)跟著小編一起來(lái)學(xué)習(xí)吧!

1 微小謎題

上周在工作中我提交了對(duì)一個(gè)類(lèi)的微小改動(dòng),實(shí)現(xiàn)了toString()方法,讓日志更容易理解。令我吃驚的是,這個(gè)變動(dòng)導(dǎo)致類(lèi)的單元測(cè)試覆蓋率下降了5%。我知道所有的新代碼都被現(xiàn)有測(cè)試所覆蓋。那么是哪錯(cuò)了呢?在比較覆蓋率報(bào)告的時(shí)候,一個(gè)眼尖的同事注意到hashCode()在變更前被測(cè)試覆蓋,而變更后卻沒(méi)有。這就說(shuō)得通了:默認(rèn)toString()方法調(diào)用了hashCode()方法。

public String toString() {
    return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

在重寫(xiě)toString()之后,自定義hashCode()不再被調(diào)用。我遺漏了一項(xiàng)測(cè)試。

每個(gè)人都了解toString()方法,但是……

2 默認(rèn)hashCode()方法是怎么實(shí)現(xiàn)的?

默認(rèn)hashCode()方法的所返回的值叫做標(biāo)識(shí)散列碼(identity hash code)。從現(xiàn)在開(kāi)始,我將使用這個(gè)術(shù)語(yǔ)來(lái)區(qū)分它與重寫(xiě)hashCode()方法返回的散列碼。注:即使類(lèi)重寫(xiě)了hashCode(),你仍然可以通過(guò)System.identityHashCode(o)來(lái)獲得對(duì)象o的標(biāo)識(shí)散列碼。

使用內(nèi)存地址的整型表示作為標(biāo)識(shí)散列碼是常識(shí),也是J2SE文檔所暗示的:

……通常是通過(guò)將對(duì)象的內(nèi)部地址轉(zhuǎn)換為整數(shù)來(lái)實(shí)現(xiàn)的,但這種實(shí)現(xiàn)技術(shù)并非Java編程語(yǔ)言所要求。

盡管如此,看起來(lái)還是有問(wèn)題的,因?yàn)榉椒s定要求:

在Java應(yīng)用程序執(zhí)行期間,在同一對(duì)象上多次調(diào)用hashCode()方法時(shí),hashCode()方法必須返回同一個(gè)值,無(wú)論調(diào)用的時(shí)機(jī)如何。

考慮到JVM會(huì)重新定位對(duì)象(例如在由晉升或壓縮導(dǎo)致的GC周期中)。在計(jì)算對(duì)象的標(biāo)識(shí)散列碼之后,我們必須能以某種方式重新得到這個(gè)值,即使發(fā)生了對(duì)象重定位。

一種可能性是在第一次調(diào)用hashCode()時(shí)獲取對(duì)象的當(dāng)前內(nèi)存位置,然后和對(duì)象一起保存,比如保存到對(duì)象頭。這樣即使對(duì)象被移動(dòng)到不同的位置,它仍然留有最初的標(biāo)識(shí)散列碼。這種方法的一個(gè)隱患是:它無(wú)法阻止兩個(gè)不同對(duì)象具有相同的標(biāo)識(shí)散列碼。但Java規(guī)范允許這種情況發(fā)生。

最好的確認(rèn)方法是查看源代碼。不幸的是,默認(rèn)的java.lang.Object::hashCode()是一個(gè)本地方法。Listing 2: Object::hashCode是本地方法

public native int hashCode();

3 真正的hashCode()請(qǐng)出來(lái)

要注意的是,標(biāo)識(shí)散列碼的實(shí)現(xiàn)依賴于JVM。因?yàn)槲抑挥懻揙penJDK源代碼,所以我提到JVM時(shí),總是指OpenJDK這一特定實(shí)現(xiàn)。代碼鏈接指向代碼倉(cāng)庫(kù)的Hotspot子目錄。我認(rèn)為這份代碼中的大部分也適用于Oracle JVM,當(dāng)然在個(gè)別地方可能(實(shí)際上)是不同的(稍后會(huì)詳細(xì)介紹)。

OpenJDK定義了hashCode()入口點(diǎn),在源代碼src/share/vm/prims/jvm.h和src/share/vm/prims/jvm.cpp中:

508 JVM_ENTRY(jint, JVM_IHashCode(JNIEnv* env, jobject handle))
509   JVMWrapper("JVM_IHashCode");
510   // as implemented in the classic virtual machine; return 0 if object is NULL511   return handle == NULL ? 0 : ObjectSynchronizer::FastHashCode (THREAD, JNIHandles::resolve_non_null(handle)) ;
512 JVM_END

identity_hash_value_for也調(diào)用了ObjectSynchronizer::FastHashCode(),前者被其他一些地方(如System.identityHashCode())調(diào)用。

708 intptr_t ObjectSynchronizer::identity_hash_value_for(Handle obj) {
709   return FastHashCode (Thread::current(), obj()) ;
710 }

有人可能簡(jiǎn)單的認(rèn)為ObjectSynchronizer::FastHashCode()的做法類(lèi)似:

if (obj.hash() == 0) {
    obj.set_hash(generate_new_hash());
}return obj.hash();

但實(shí)際上它是包含上百行代碼、看起來(lái)復(fù)雜得多的函數(shù)。不過(guò)我們可以發(fā)現(xiàn)一些“如果沒(méi)有則生成”(if-not-exists-generate)代碼,比如:

685   mark = monitor->header();
...
687   hash = mark->hash();
688   if (hash == 0) {
689     hash = get_next_hash(Self, obj);
...
701   }
...
703   return hash;

這似乎證實(shí)了我們的假設(shè)。現(xiàn)在讓我們暫時(shí)忽略管程(monitor),只要知道它可以提供對(duì)象頭。對(duì)象頭保存在變量mark中。mark是指向markOop實(shí)例的指針,markOop表示位于對(duì)象頭中低地址的標(biāo)記字(mark word)。因此hashCode()的算法是:嘗試得到標(biāo)記字中記錄的散列碼。如果沒(méi)有,用get_next_hash()生成一個(gè),保存然后返回。

4 標(biāo)識(shí)散列碼的生成

如我們所見(jiàn),散列碼由get_next_hash()生成。這個(gè)函數(shù)提供了6種計(jì)算方法,根據(jù)全局配置hashCode選擇使用哪一個(gè)。

  1. 使用隨機(jī)數(shù)。

  2. 基于對(duì)象的內(nèi)存地址計(jì)算。

  3. 硬編碼為1(用于測(cè)試)。

  4. 從一個(gè)序列生成。

  5. 使用對(duì)象的內(nèi)存地址,轉(zhuǎn)換為int類(lèi)型。

  6. 使用線程狀態(tài)和xorshift結(jié)合。

默認(rèn)方法是哪一個(gè)?OpenJDK 8使用了方法5,依據(jù)是global.hpp:

1127   product(intx, hashCode, 5,                                                \
1128           "(Unstable) select hashCode generation algorithm")                \

OpenJDK 9使用相同的默認(rèn)值。查看以前的版本,OpenJDK 7和6都使用了第一個(gè)方法:隨機(jī)數(shù)。

所以,除非我找錯(cuò)了源代碼,否則OpenJDK中默認(rèn)hashCode()方法的實(shí)現(xiàn),和對(duì)象內(nèi)存地址無(wú)關(guān),至少?gòu)腛penJDK 6開(kāi)始就是這樣。

5 對(duì)象頭和同步

讓我們回顧幾個(gè)之前沒(méi)有考慮的地方。首先ObjectSynchronizer::FastHashCode()似乎過(guò)于復(fù)雜,使用了超過(guò)100行代碼來(lái)執(zhí)行我們認(rèn)為是平凡的“得到或生成”(get-or-generate)操作。第二,管程是什么,它為什么擁有對(duì)象頭?

查看標(biāo)記詞的結(jié)構(gòu)是一個(gè)取得進(jìn)展的好的起點(diǎn)。在OpenJDK中,它是這樣的:

30 // The markOop describes the header of an object.31 //32 // Note that the mark is not a real oop but just a word.33 // It is placed in the oop hierarchy for historical reasons.34 //35 // Bit-format of an object header (most significant first, big endian layout below):36 //37 //  32 bits:38 //  --------39 //             hash:25 ------------>| age:4    biased_lock:1 lock:2 (normal object)40 //             JavaThread*:23 epoch:2 age:4    biased_lock:1 lock:2 (biased object)41 //             size:32 ------------------------------------------>| (CMS free block)42 //             PromotedObject*:29 ---------->| promo_bits:3 ----->| (CMS promoted object)43 //44 //  64 bits:45 //  --------46 //  unused:25 hash:31 -->| unused:1   age:4    biased_lock:1 lock:2 (normal object)47 //  JavaThread*:54 epoch:2 unused:1   age:4    biased_lock:1 lock:2 (biased object)48 //  PromotedObject*:61 --------------------->| promo_bits:3 ----->| (CMS promoted object)49 //  size:64 ----------------------------------------------------->| (CMS free block)50 //51 //  unused:25 hash:31 -->| cms_free:1 age:4    biased_lock:1 lock:2 (COOPs && normal object)52 //  JavaThread*:54 epoch:2 cms_free:1 age:4    biased_lock:1 lock:2 (COOPs && biased object)53 //  narrowOop:32 unused:24 cms_free:1 unused:4 promo_bits:3 ----->| (COOPs && CMS promoted object)54 //  unused:21 size:35 -->| cms_free:1 unused:7 ------------------>| (COOPs && CMS free block)

在32位機(jī)器和64位機(jī)器上,標(biāo)記字的格式略有不同。后者有兩個(gè)變體,取決于是否啟用了壓縮對(duì)象指針(Compressed Object Pointer)。Oracle JVM和OpenJDK 8都是默認(rèn)啟用的。

因此對(duì)象頭可能與一個(gè)內(nèi)存塊或一個(gè)實(shí)際的對(duì)象關(guān)聯(lián),存在多種狀態(tài)。在最簡(jiǎn)單的情況下(“普通對(duì)象”),標(biāo)識(shí)散列碼直接存儲(chǔ)在對(duì)象頭的低地址中。

但在其他狀態(tài)下,對(duì)象頭包含一個(gè)指向JavaThread或PromotedObject的指針。更復(fù)雜的是:如果我們把唯一散列碼放到一個(gè)“普通對(duì)象”中,它會(huì)被移走嗎?移動(dòng)到哪?如果對(duì)象是有偏向的(biased),我們可以從哪里獲得或設(shè)置標(biāo)識(shí)散列碼?什么又是有偏向的對(duì)象(biased object)呢?

讓我們?cè)囍卮疬@些問(wèn)題。

6 偏向鎖

偏向?qū)ο罂雌饋?lái)是偏向鎖的結(jié)果。這是從HotSpot 6起默認(rèn)啟用的一個(gè)特性,試圖減少鎖定對(duì)象的成本。鎖定操作是昂貴的,它的實(shí)現(xiàn)通常依賴于原子CPU指令(CAS),以便安全地處理來(lái)自不同線程的鎖定和解鎖請(qǐng)求。根據(jù)觀察,在大多數(shù)應(yīng)用程序中,大多數(shù)對(duì)象只被一個(gè)線程鎖定,因此為原子操作付出的成本常常被浪費(fèi)了。為了避免這種情況,帶有偏向鎖的JVM允許線程將對(duì)象設(shè)置為“偏向于”自己。如果一個(gè)對(duì)象是有偏向的,線程可以鎖定和解鎖對(duì)象,而無(wú)需原子指令。只要沒(méi)有線程爭(zhēng)用同一個(gè)對(duì)象,我們就會(huì)得到性能提升。

對(duì)象頭中的偏向鎖位(biased_lock bit)表示對(duì)象是否偏向于JavaThread*所指向的線程。鎖定位(lock bit)表示該對(duì)象是否被鎖定。

正是因?yàn)镺penJDK的偏向鎖實(shí)現(xiàn)需要在標(biāo)記字中寫(xiě)入一個(gè)指針,它需要重新定位真正的標(biāo)記字(其中包含標(biāo)識(shí)散列碼)。

這可以解釋FasttHashCode中額外的復(fù)雜性。對(duì)象頭不僅包含標(biāo)識(shí)散列碼,也包含鎖定狀態(tài)(比如指向鎖持有者線程的指針)。因此我們需要考慮所有情況,并找到標(biāo)識(shí)散列碼存儲(chǔ)的位置。

讓我們來(lái)讀讀FasttHashCode。我們發(fā)現(xiàn)的第一件事是:

601 intptr_t ObjectSynchronizer::FastHashCode (Thread * Self, oop obj) {
602   if (UseBiasedLocking) {
610     if (obj->mark()->has_bias_pattern()) {
          ...
617       BiasedLocking::revoke_and_rebias(hobj, false, JavaThread::current());
          ...
619       assert(!obj->mark()->has_bias_pattern(), "biases should be revoked by now");
620     }
621   }

等等,它只是撤銷(xiāo)了現(xiàn)有的偏向性,并禁用了對(duì)象上的偏向鎖(false意味著不要嘗試重置偏向性)??唇酉聛?lái)的幾行,這確實(shí)是一個(gè)不變量:

637   // object should remain ineligible for biased locking638   assert (!mark->has_bias_pattern(), "invariant") ;

如果我沒(méi)看錯(cuò),這意味著簡(jiǎn)單地請(qǐng)求對(duì)象的標(biāo)識(shí)散列碼將禁用偏向鎖,這將強(qiáng)制要求鎖定對(duì)象必須使用昂貴的原子指令,即使只有一個(gè)線程。

7 為什么偏向鎖和標(biāo)識(shí)散列碼沖突?

通過(guò)重寫(xiě)hashCode()方法將偏向鎖性能提高4倍的方法步驟

要回答這個(gè)問(wèn)題,我們必須了解標(biāo)記字(包含標(biāo)識(shí)散列碼)可能存在的位置,這取決于對(duì)象的鎖的狀態(tài)。下面這張來(lái)自于HotSpot Wiki的圖展示了轉(zhuǎn)換過(guò)程:  我的(不可靠)推理如下。

對(duì)于圖頂部的4種狀態(tài),OpenJDK將能夠使用“輕”鎖表示。在最簡(jiǎn)單的情況下(沒(méi)有鎖),這意味著將標(biāo)識(shí)散列碼和其他數(shù)據(jù)直接放在對(duì)象的標(biāo)記字中:

46 //  unused:25 hash:31 -->| unused:1   age:4    biased_lock:1 lock:2 (normal object)

在更復(fù)雜的情況下,它需要這個(gè)空間來(lái)保存指向“鎖對(duì)象”的指針。因此,標(biāo)記字將被“替換”,放到其他地方。

既然只有一個(gè)線程嘗試鎖定對(duì)象,指針實(shí)際上會(huì)指向線程堆棧中的某個(gè)內(nèi)存位置。這么做有兩個(gè)優(yōu)點(diǎn):訪問(wèn)速度快(沒(méi)有爭(zhēng)用或內(nèi)存訪問(wèn)協(xié)調(diào)),并且能夠讓線程確定它擁有鎖(因?yàn)閮?nèi)存位置指向自己的堆棧)。

但這并非在所有情況下都有效。如果存在對(duì)象爭(zhēng)用(例如許多線程都會(huì)執(zhí)行到的同步語(yǔ)句),我們將需要一個(gè)更復(fù)雜的結(jié)構(gòu),不僅可以保存對(duì)象頭副本,也保存一組等待者。如果線程執(zhí)行object.wait(),就會(huì)出現(xiàn)對(duì)等待者列表的類(lèi)似需求。

這個(gè)更豐富的數(shù)據(jù)結(jié)構(gòu)就是ObjectMonitor,在圖中稱為“重量級(jí)”管程。對(duì)象頭不再指向“被替換的標(biāo)記字”,而是指向一個(gè)實(shí)際的對(duì)象(管程)。這時(shí)訪問(wèn)標(biāo)識(shí)散列碼需要“擴(kuò)張管程(inflate the monitor)”:跟蹤指針得到對(duì)象,讀取或修改包含被替換標(biāo)記字的域。這個(gè)操作更加昂貴,而且需要協(xié)調(diào)。

FasttHashCode確實(shí)有工作要做。

L640到L680處理查找對(duì)象頭并檢查緩存的標(biāo)識(shí)散列碼。我相信存在一個(gè)快速路徑來(lái)探測(cè)不需要擴(kuò)張管程的情況。

從L682開(kāi)始需要咬緊牙關(guān):

682   // Inflate the monitor to set hash code683   monitor = ObjectSynchronizer::inflate(Self, obj);

684   // Load displaced header and check it has hash code685   mark = monitor->header();
...
687   hash = mark->hash();

此時(shí),如果標(biāo)識(shí)散列碼存在(hash != 0),JVM可以直接返回。否則需要從get_next_hash()中得到散列碼,并安全地存儲(chǔ)在ObjectMonitor保存的對(duì)象頭中。

這似乎提供了一個(gè)合理的解釋,為什么在不覆蓋默認(rèn)實(shí)現(xiàn)的對(duì)象上調(diào)用hashCode()導(dǎo)致對(duì)象不符合偏向鎖的條件:

  • 為了在重定位后保持對(duì)象的標(biāo)識(shí)散列碼不變,需要將標(biāo)識(shí)散列碼存儲(chǔ)在對(duì)象頭中。

  • 請(qǐng)求標(biāo)識(shí)散列碼的線程未必關(guān)心對(duì)象是否鎖定,但上它們實(shí)際上共享了鎖機(jī)制使用的數(shù)據(jù)結(jié)構(gòu)。這種機(jī)制是一個(gè)復(fù)雜怪獸,它不僅自身要發(fā)生變化,還要移動(dòng)(替換)對(duì)象頭。

  • 偏向鎖能夠在不使用原子操作的情況下進(jìn)行鎖定和解鎖操作。偏向鎖是高效的,如果只有一個(gè)線程鎖定對(duì)象。我們可以將鎖狀態(tài)記錄到標(biāo)記字中。我不能100%肯定,但是我認(rèn)為既然其他線程可能會(huì)讀取標(biāo)識(shí)散列碼,即使只有一個(gè)線程需要鎖定,標(biāo)記字也會(huì)發(fā)生爭(zhēng)用,并需要原子操作來(lái)保證準(zhǔn)確。這否定了偏向鎖的全部意義。

8 回顧

  • 默認(rèn)的hashCode()實(shí)現(xiàn)(標(biāo)識(shí)哈希碼)和對(duì)象的內(nèi)存地址無(wú)關(guān),至少在OpenJDK中是這樣的。在OpenJDK 6和7中,它是一個(gè)隨機(jī)生成的數(shù)字。在OpenJDK 8和9中,它是一個(gè)基于線程狀態(tài)的數(shù)字。這里有一個(gè)測(cè)試得出了相同的結(jié)論。

    • 證明“依賴于實(shí)現(xiàn)”的警告并非虛談:Azul Zing確實(shí)從對(duì)象的內(nèi)存地址生成標(biāo)識(shí)散列碼。

  • 在HotSpot中,標(biāo)識(shí)散列碼只生成一次,然后緩存在對(duì)象頭的標(biāo)記字中。

    • Zing使用了不同的方案來(lái)保證散列碼在對(duì)象重定位后的一致的。他們?cè)趯?duì)象重定向時(shí)才保存標(biāo)識(shí)散列碼的值。這個(gè)時(shí)候散列碼被保存在pre-header中。

  • 在HotSpot中,調(diào)用默認(rèn)值hashCode()或System.identityHashCode()將使對(duì)象的鎖失去偏向性。

    • 這意味著如果你對(duì)沒(méi)有爭(zhēng)用的對(duì)象進(jìn)行同步(synchronized),最好重寫(xiě)默認(rèn)的hashCode()實(shí)現(xiàn),否則將錯(cuò)過(guò)JVM優(yōu)化。

  • 在HotSpot中,可以禁用單個(gè)對(duì)象的偏向鎖。

    • 這是非常有用的。我曾見(jiàn)過(guò)應(yīng)用程序在爭(zhēng)用的生產(chǎn)者-消費(fèi)者隊(duì)列中使用過(guò)多的偏向鎖,這帶來(lái)的麻煩比好處多,所以我們完全禁用了這個(gè)特性。實(shí)際上,我們可以通過(guò)在特定對(duì)象或類(lèi)上調(diào)用System.identityHashCode()來(lái)實(shí)現(xiàn)這一點(diǎn)。

  • 我發(fā)現(xiàn)HotSpot沒(méi)有標(biāo)志選擇默認(rèn)的hashCode生成器,所以試驗(yàn)其他生成器可能需要編譯源代碼。

    • 說(shuō)實(shí)話我沒(méi)仔細(xì)看。Michael Rasmussen善意地指出-XX:hashCode=2可以用來(lái)更改默認(rèn)值。謝謝!

9 基準(zhǔn)測(cè)試

我編寫(xiě)了一個(gè)簡(jiǎn)單的JMH工具來(lái)驗(yàn)證這些結(jié)論。

基準(zhǔn)測(cè)試所做的事情類(lèi)似:

object.hashCode();while(true) {synchronized(object) {
        counter++;
    }
}

第一種配置(withIdHash)在使用標(biāo)識(shí)散列碼的對(duì)象上同步,我們預(yù)計(jì)調(diào)用hashCode()將導(dǎo)致偏向鎖被禁用。第二種配置(withoutIdHash)實(shí)現(xiàn)了自定義散列碼,因此不會(huì)禁用偏向鎖。每個(gè)配置先用一個(gè)線程運(yùn)行,然后用兩個(gè)線程(帶有后綴“Contended”)。

順便說(shuō)一下,我們必須啟用-XX:BiasedLockingStartupDelay=0,否則JVM將等待4s時(shí)間才觸發(fā)優(yōu)化,這將影響測(cè)試效果。

第一次執(zhí)行:

Benchmark Mode Cnt Score Error Units BiasedLockingBenchmark.withIdHash thrpt 100 35168,021 ± 230,252 ops/ms BiasedLockingBenchmark.withoutIdHash thrpt 100 173742,468 ± 4364,491 ops/ms BiasedLockingBenchmark.withIdHashContended thrpt 100 22478,109 ± 1650,649 ops/ms BiasedLockingBenchmark.withoutIdHashContended thrpt 100 20061,973 ± 786,021 ops/ms

我們可以看到,使用自定義散列碼使鎖定和解鎖循環(huán)比使用標(biāo)識(shí)散列碼(禁用偏向鎖)快4倍。當(dāng)兩個(gè)線程爭(zhēng)用鎖時(shí),偏置鎖將被禁用,因此兩種散列方法之間沒(méi)有顯著差異。

第二次運(yùn)行,禁用所有配置中的偏向鎖(-XX:-UseBiasedLocking)。

Benchmark Mode Cnt Score Error Units BiasedLockingBenchmark.withIdHash thrpt 100 37374,774 ± 204,795 ops/ms BiasedLockingBenchmark.withoutIdHash thrpt 100 36961,826 ± 214,083 ops/ms BiasedLockingBenchmark.withIdHashContended thrpt 100 18349,906 ± 1246,372 ops/ms BiasedLockingBenchmark.withoutIdHashContended thrpt 100 18262,290 ± 1371,588 ops/ms

散列方法不再有任何影響,withoutIdHash也失去了它的優(yōu)勢(shì)。

(所有的基準(zhǔn)測(cè)試都運(yùn)行在一臺(tái) 2.7 GHz Intel Core i5電腦上。)

10 參考文獻(xiàn)

這些猜想以及我對(duì)JVM源代碼的理解,來(lái)自于對(duì)關(guān)于布局、偏向鎖等不同資料的拼湊。主要的資料有:

  • https://blogs.oracle.com/dave/entry/biased_locking_in_hotspot

  • http://fuseyism.com/openjdk/cvmi/java2vm.xhtml

  • http://www.dcs.gla.ac.uk/~jsinger/pdfs/sicsa_openjdk/OpenJDKArchitecture.pdf

  • https://www.infoq.com/articles/Introduction-to-HotSpot

  • http://blog.takipi.com/5-things-you-didnt-know-about-synchronization-in-java-and-scala/#comment-1006598967

  • http://www.azulsystems.com/blog/cliff/2010-01-09-biased-locking

  • https://dzone.com/articles/why-should-you-care-about-equals-and-hashcode

  • https://wiki.openjdk.java.net/display/HotSpot/Synchronization

  • https://mechanical-sympathy.blogspot.com.es/2011/11/biased-locking-osr-and-benchmarking-fun.html

11 附錄:基準(zhǔn)測(cè)試代碼

package com.github.srvaroa.jmh;

import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;

import java.util.concurrent.TimeUnit;

@State(Scope.Benchmark)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Warmup(iterations = 4)
@Fork(value = 5, jvmArgsAppend = {"-XX:-UseBiasedLocking", "-XX:BiasedLockingStartupDelay=0"})
public class BiasedLockingBenchmark {

    int unsafeCounter = 0;
    Object withIdHash;
    Object withoutIdHash;

    @Setup
    public void setup() {
        withIdHash = new Object();
        withoutIdHash = new Object() {
            @Override
            public int hashCode() {
                return 1;
            }
        };
        withIdHash.hashCode();
        withoutIdHash.hashCode();
    }

    @Benchmark
    public void withIdHash(Blackhole bh) {
        synchronized(withIdHash) {
            bh.consume(unsafeCounter++);
        }
    }

    @Benchmark
    public void withoutIdHash(Blackhole bh) {
        synchronized(withoutIdHash) {
            bh.consume(unsafeCounter++);
        }
    }

    @Benchmark
    @Threads(2)
    public void withoutIdHashContended(Blackhole bh) {
        synchronized(withoutIdHash) {
            bh.consume(unsafeCounter++);
        }
    }

    @Benchmark
    @Threads(2)
    public void withIdHashContended(Blackhole bh) {
        synchronized(withIdHash) {
            bh.consume(unsafeCounter++);
        }
    }

}

到此,關(guān)于“通過(guò)重寫(xiě)hashCode()方法將偏向鎖性能提高4倍的方法步驟”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注億速云網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)?lái)更多實(shí)用的文章!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI