小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

聊聊并發(fā)(二)Java SE1.6中的Synchronized

 codingparty 2016-01-21

本文屬作者原創(chuàng),原文發(fā)表于InfoQ:http://www./cn/articles/java-se-16-synchronized

1 引言

在多線程并發(fā)編程中Synchronized一直是元老級角色,很多人都會稱呼它為重量級鎖,但是隨著Java SE1.6對Synchronized進(jìn)行了各種優(yōu)化之后,有些情況下它并不那么重了,本文詳細(xì)介紹了Java SE1.6中為了減少獲得鎖和釋放鎖帶來的性能消耗而引入的偏向鎖和輕量級鎖,以及鎖的存儲結(jié)構(gòu)和升級過程。

2 術(shù)語定義

術(shù)語 英文 說明
CAS Compare and Swap 比較并設(shè)置。用于在硬件層面上提供原子性操作。在 Intel 處理器中,比較并交換通過指令cmpxchg實(shí)現(xiàn)。比較是否和給定的數(shù)值一致,如果一致則修改,不一致則不修改。

3 同步的基礎(chǔ)

Java中的每一個對象都可以作為鎖。

  • 對于同步方法,鎖是當(dāng)前實(shí)例對象。
  • 對于靜態(tài)同步方法,鎖是當(dāng)前對象的Class對象。
  • 對于同步方法塊,鎖是Synchonized括號里配置的對象。

當(dāng)一個線程試圖訪問同步代碼塊時,它首先必須得到鎖,退出或拋出異常時必須釋放鎖。那么鎖存在哪里呢?鎖里面會存儲什么信息呢?

4 同步的原理

JVM規(guī)范規(guī)定JVM基于進(jìn)入和退出Monitor對象來實(shí)現(xiàn)方法同步和代碼塊同步,但兩者的實(shí)現(xiàn)細(xì)節(jié)不一樣。代碼塊同步是使用monitorenter和monitorexit指令實(shí)現(xiàn),而方法同步是使用另外一種方式實(shí)現(xiàn)的,細(xì)節(jié)在JVM規(guī)范里并沒有詳細(xì)說明,但是方法的同步同樣可以使用這兩個指令來實(shí)現(xiàn)。monitorenter指令是在編譯后插入到同步代碼塊的開始位置,而monitorexit是插入到方法結(jié)束處和異常處, JVM要保證每個monitorenter必須有對應(yīng)的monitorexit與之配對。任何對象都有一個 monitor 與之關(guān)聯(lián),當(dāng)且一個monitor 被持有后,它將處于鎖定狀態(tài)。線程執(zhí)行到 monitorenter 指令時,將會嘗試獲取對象所對應(yīng)的 monitor 的所有權(quán),即嘗試獲得對象的鎖。

4.1 Java對象頭

鎖存在Java對象頭里。如果對象是數(shù)組類型,則虛擬機(jī)用3個Word(字寬)存儲對象頭,如果對象是非數(shù)組類型,則用2字寬存儲對象頭。在32位虛擬機(jī)中,一字寬等于四字節(jié),即32bit。

長度 內(nèi)容 說明
32/64bit Mark Word 存儲對象的hashCode或鎖信息等。
32/64bit Class Metadata Address 存儲到對象類型數(shù)據(jù)的指針
32/64bit Array length 數(shù)組的長度(如果當(dāng)前對象是數(shù)組)

Java對象頭里的Mark Word里默認(rèn)存儲對象的HashCode,分代年齡和鎖標(biāo)記位。32位JVM的Mark Word的默認(rèn)存儲結(jié)構(gòu)如下:

25 bit 4bit 1bit是否是偏向鎖 2bit鎖標(biāo)志位
無鎖狀態(tài) 對象的hashCode 對象分代年齡 0 01

在運(yùn)行期間Mark Word里存儲的數(shù)據(jù)會隨著鎖標(biāo)志位的變化而變化。Mark Word可能變化為存儲以下4種數(shù)據(jù):

鎖狀態(tài)

25 bit

4bit

1bit 2bit
23bit 2bit 是否是偏向鎖 鎖標(biāo)志位
輕量級鎖 指向棧中鎖記錄的指針 00
重量級鎖 指向互斥量(重量級鎖)的指針 10
GC標(biāo)記 11
偏向鎖 線程ID Epoch 對象分代年齡 1 01

在64位虛擬機(jī)下,Mark Word是64bit大小的,其存儲結(jié)構(gòu)如下:

鎖狀態(tài)

25bit

31bit

1bit

4bit

1bit 2bit
cms_free 分代年齡 偏向鎖 鎖標(biāo)志位
無鎖 unused hashCode 0 01
偏向鎖 ThreadID(54bit) Epoch(2bit) 1 01

4.2 鎖的升級

Java SE1.6為了減少獲得鎖和釋放鎖所帶來的性能消耗,引入了“偏向鎖”和“輕量級鎖”,所以在Java SE1.6里鎖一共有四種狀態(tài),無鎖狀態(tài),偏向鎖狀態(tài),輕量級鎖狀態(tài)和重量級鎖狀態(tài),它會隨著競爭情況逐漸升級。鎖可以升級但不能降級,意味著偏向鎖升級成輕量級鎖后不能降級成偏向鎖。這種鎖升級卻不能降級的策略,目的是為了提高獲得鎖和釋放鎖的效率,下文會詳細(xì)分析。

4.3 偏向鎖

Hotspot的作者經(jīng)過以往的研究發(fā)現(xiàn)大多數(shù)情況下鎖不僅不存在多線程競爭,而且總是由同一線程多次獲得,為了讓線程獲得鎖的代價更低而引入了偏向鎖。當(dāng)一個線程訪問同步塊并獲取鎖時,會在對象頭和棧幀中的鎖記錄里存儲鎖偏向的線程ID,以后該線程在進(jìn)入和退出同步塊時不需要花費(fèi)CAS操作來加鎖和解鎖,而只需簡單的測試一下對象頭的Mark Word里是否存儲著指向當(dāng)前線程的偏向鎖,如果測試成功,表示線程已經(jīng)獲得了鎖,如果測試失敗,則需要再測試下Mark Word中偏向鎖的標(biāo)識是否設(shè)置成1(表示當(dāng)前是偏向鎖),如果沒有設(shè)置,則使用CAS競爭鎖,如果設(shè)置了,則嘗試使用CAS將對象頭的偏向鎖指向當(dāng)前線程。

偏向鎖的撤銷:偏向鎖使用了一種等到競爭出現(xiàn)才釋放鎖的機(jī)制,所以當(dāng)其他線程嘗試競爭偏向鎖時,持有偏向鎖的線程才會釋放鎖。偏向鎖的撤銷,需要等待全局安全點(diǎn)(在這個時間點(diǎn)上沒有字節(jié)碼正在執(zhí)行),它會首先暫停擁有偏向鎖的線程,然后檢查持有偏向鎖的線程是否活著,如果線程不處于活動狀態(tài),則將對象頭設(shè)置成無鎖狀態(tài),如果線程仍然活著,擁有偏向鎖的棧會被執(zhí)行,遍歷偏向?qū)ο蟮逆i記錄,棧中的鎖記錄和對象頭的Mark Word要么重新偏向于其他線程,要么恢復(fù)到無鎖或者標(biāo)記對象不適合作為偏向鎖,最后喚醒暫停的線程。下圖中的線程1演示了偏向鎖初始化的流程,線程2演示了偏向鎖撤銷的流程。

偏向鎖的撤銷

關(guān)閉偏向鎖:偏向鎖在Java 6和Java 7里是默認(rèn)啟用的,但是它在應(yīng)用程序啟動幾秒鐘之后才激活,如有必要可以使用JVM參數(shù)來關(guān)閉延遲-XX:BiasedLockingStartupDelay = 0。如果你確定自己應(yīng)用程序里所有的鎖通常情況下處于競爭狀態(tài),可以通過JVM參數(shù)關(guān)閉偏向鎖-XX:-UseBiasedLocking=false,那么默認(rèn)會進(jìn)入輕量級鎖狀態(tài)。

4.4 輕量級鎖

輕量級鎖加鎖:線程在執(zhí)行同步塊之前,JVM會先在當(dāng)前線程的棧楨中創(chuàng)建用于存儲鎖記錄的空間,并將對象頭中的Mark Word復(fù)制到鎖記錄中,官方稱為Displaced Mark Word。然后線程嘗試使用CAS將對象頭中的Mark Word替換為指向鎖記錄的指針。如果成功,當(dāng)前線程獲得鎖,如果失敗,表示其他線程競爭鎖,當(dāng)前線程便嘗試使用自旋來獲取鎖。

輕量級鎖解鎖:輕量級解鎖時,會使用原子的CAS操作來將Displaced Mark Word替換回到對象頭,如果成功,則表示沒有競爭發(fā)生。如果失敗,表示當(dāng)前鎖存在競爭,鎖就會膨脹成重量級鎖。下圖是兩個線程同時爭奪鎖,導(dǎo)致鎖膨脹的流程圖。
輕量級鎖

因?yàn)樽孕龝腃PU,為了避免無用的自旋(比如獲得鎖的線程被阻塞住了),一旦鎖升級成重量級鎖,就不會再恢復(fù)到輕量級鎖狀態(tài)。當(dāng)鎖處于這個狀態(tài)下,其他線程試圖獲取鎖時,都會被阻塞住,當(dāng)持有鎖的線程釋放鎖之后會喚醒這些線程,被喚醒的線程就會進(jìn)行新一輪的奪鎖之爭。

5 鎖的優(yōu)缺點(diǎn)對比

優(yōu)點(diǎn)

缺點(diǎn)

適用場景

偏向鎖

加鎖和解鎖不需要額外的消耗,和執(zhí)行非同步方法比僅存在納秒級的差距。

如果線程間存在鎖競爭,會帶來額外的鎖撤銷的消耗。

適用于只有一個線程訪問同步塊場景。

輕量級鎖

競爭的線程不會阻塞,提高了程序的響應(yīng)速度。

如果始終得不到鎖競爭的線程使用自旋會消耗CPU。

追求響應(yīng)時間。

同步塊執(zhí)行速度非???。

重量級鎖

線程競爭不使用自旋,不會消耗CPU。

線程阻塞,響應(yīng)時間緩慢。

追求吞吐量。

同步塊執(zhí)行速度較長。

6 參考源碼

本文一些內(nèi)容參考了HotSpot源碼 。對象頭源碼markOop.hpp。偏向鎖源碼biasedLocking.cpp。以及其他源碼ObjectMonitor.cpp和BasicLock.cpp。

7 參考資料

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多