您好,登錄后才能下訂單哦!
這篇文章主要介紹“通過(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í)吧!
上周在工作中我提交了對(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()方法,但是……
默認(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();
要注意的是,標(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è),保存然后返回。
如我們所見(jiàn),散列碼由get_next_hash()生成。這個(gè)函數(shù)提供了6種計(jì)算方法,根據(jù)全局配置hashCode選擇使用哪一個(gè)。
使用隨機(jī)數(shù)。
基于對(duì)象的內(nèi)存地址計(jì)算。
硬編碼為1(用于測(cè)試)。
從一個(gè)序列生成。
使用對(duì)象的內(nèi)存地址,轉(zhuǎn)換為int類(lèi)型。
使用線程狀態(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)始就是這樣。
讓我們回顧幾個(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)題。
偏向?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è)線程。
要回答這個(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)確。這否定了偏向鎖的全部意義。
默認(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)值。謝謝!
我編寫(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電腦上。)
這些猜想以及我對(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
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í)用的文章!
免責(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)容。