Synchronized底層原理學(xué)習(xí)筆記(二)

最初學(xué)習(xí)Java的時(shí)候榜配,遇到多線程我們會(huì)知道synchronized愿待,對(duì)于當(dāng)時(shí)的我們來(lái)說(shuō)synchronized是保證了多線程之間的同步硝皂,也成為了我們解決多線程情況的常用手段盯另。但是谢床,隨著我們學(xué)習(xí)的進(jìn)行我們知道synchronized是一個(gè)重量級(jí)鎖兄一,相對(duì)于Lock,它會(huì)顯得那么笨重识腿,以至于我們認(rèn)為它不是那么的高效而慢慢摒棄它出革。
但是,隨著Javs SE 1.6對(duì)synchronized進(jìn)行的各種優(yōu)化后渡讼,synchronized并不會(huì)顯得那么重了骂束。下面跟隨LZ一起來(lái)探索synchronized的實(shí)現(xiàn)機(jī)制、Java是如何對(duì)它進(jìn)行了優(yōu)化成箫、鎖優(yōu)化機(jī)制展箱、鎖的存儲(chǔ)結(jié)構(gòu)和升級(jí)過(guò)程;
一.synchronized的實(shí)現(xiàn)機(jī)制
Java對(duì)象頭和monitor是實(shí)現(xiàn)synchronized的基礎(chǔ)!下面就這兩個(gè)概念來(lái)做詳細(xì)介紹蹬昌。
1.Java對(duì)象頭:Hotspot虛擬機(jī)的對(duì)象頭主要包括兩部分?jǐn)?shù)據(jù):Mark Word(標(biāo)記字段)混驰、Klass Pointer(類型指針)

Klass Point是對(duì)象指向它的類元數(shù)據(jù)的指針,虛擬機(jī)通過(guò)這個(gè)指針來(lái)確定這個(gè)對(duì)象是哪個(gè)類的實(shí)例;
Mark Word用于存儲(chǔ)對(duì)象自身的運(yùn)行時(shí)數(shù)據(jù)栖榨,如哈希碼(HashCode)竞慢、GC分代年齡、鎖狀態(tài)標(biāo)志治泥、線程持有的鎖筹煮、偏向線程 ID、偏向時(shí)間戳等等,它是實(shí)現(xiàn)輕量級(jí)鎖和偏向鎖的關(guān)鍵.

2.什么是Monitor居夹?我們可以把它理解為一個(gè)同步工具败潦,也可以描述為一種同步機(jī)制,它通常被描述為對(duì)象監(jiān)視器准脂。
當(dāng)多個(gè)線程同時(shí)請(qǐng)求某個(gè)對(duì)象監(jiān)視器時(shí)劫扒,對(duì)象監(jiān)視器會(huì)設(shè)置幾種狀態(tài)用來(lái)區(qū)分請(qǐng)求的線程:

Contention List:所有請(qǐng)求鎖的線程將被首先放置到該競(jìng)爭(zhēng)隊(duì)列
Entry List:Contention List中那些有資格成為候選人的線程被移到Entry List
Wait Set:那些調(diào)用wait方法被阻塞的線程被放置到Wait Set
OnDeck:任何時(shí)刻最多只能有一個(gè)線程正在競(jìng)爭(zhēng)鎖,該線程稱為OnDeck
Owner:獲得鎖的線程稱為Owner
!Owner:釋放鎖的線程
下圖就是多個(gè)線程獲取鎖的示意圖:


image.png

新請(qǐng)求鎖的線程將首先被加入到ConetentionList中狸膏,當(dāng)某個(gè)擁有鎖的線程(Owner狀態(tài))調(diào)用unlock之后沟饥,如果發(fā)現(xiàn)EntryList為空則從ContentionList中移動(dòng)線程到EntryList,下面說(shuō)明下ContentionList和EntryList的實(shí)現(xiàn)方式:

ContentionList虛擬隊(duì)列

ContentionList并不是一個(gè)真正的Queue湾戳,而只是一個(gè)虛擬隊(duì)列贤旷,原因在于ContentionList是由Node及其next指針邏輯構(gòu)成,并不存在一個(gè)Queue的數(shù)據(jù)結(jié)構(gòu)砾脑。ContentionList是一個(gè)先進(jìn)先出(FIFO)的隊(duì)列幼驶,每次新加入Node時(shí)都會(huì)在隊(duì)頭進(jìn)行,通過(guò)CAS改變第一個(gè)節(jié)點(diǎn)的的指針為新增節(jié)點(diǎn)韧衣,同時(shí)設(shè)置新增節(jié)點(diǎn)的next指向后續(xù)節(jié)點(diǎn)盅藻,而取得操作則發(fā)生在隊(duì)尾。顯然畅铭,該結(jié)構(gòu)其實(shí)是個(gè)Lock-Free的隊(duì)列氏淑。
因?yàn)橹挥蠴wner線程才能從隊(duì)尾取元素,也即線程出列操作無(wú)爭(zhēng)用硕噩,當(dāng)然也就避免了CAS的ABA問(wèn)題假残。

EntryList

EntryList與ContentionList邏輯上同屬等待隊(duì)列,ContentionList會(huì)被線程并發(fā)訪問(wèn)榴徐,為了降低對(duì)ContentionList隊(duì)尾的爭(zhēng)用守问,而建立EntryList。Owner線程在unlock時(shí)會(huì)從ContentionList中遷移線程到EntryList坑资,并會(huì)指定EntryList中的某個(gè)線程(一般為Head)為Ready(OnDeck)線程耗帕。Owner線程并不是把鎖傳遞給OnDeck線程,只是把競(jìng)爭(zhēng)鎖的權(quán)利交給OnDeck袱贮,OnDeck線程需要重新競(jìng)爭(zhēng)鎖仿便。這樣做雖然犧牲了一定的公平性,但極大的提高了整體吞吐量,在Hotspot中把OnDeck的選擇行為稱之為“競(jìng)爭(zhēng)切換”嗽仪。
OnDeck線程獲得鎖后即變?yōu)閛wner線程荒勇,無(wú)法獲得鎖則會(huì)依然留在EntryList中,考慮到公平性闻坚,在EntryList中的位置不發(fā)生變化(依然在隊(duì)頭)沽翔。如果Owner線程被wait方法阻塞,則轉(zhuǎn)移到WaitSet隊(duì)列窿凤;如果在某個(gè)時(shí)刻被notify/notifyAll喚醒仅偎,則再次轉(zhuǎn)移到EntryList。

二.java1.6之后synchronized的優(yōu)化
jdk1.6對(duì)鎖的實(shí)現(xiàn)引入了大量的優(yōu)化雳殊,如自旋鎖橘沥、適應(yīng)性自旋鎖、鎖消除夯秃、鎖粗化座咆、偏向鎖、輕量級(jí)鎖等技術(shù)來(lái)減少鎖操作的開銷仓洼。

自旋鎖

線程的阻塞和喚醒需要CPU從用戶態(tài)轉(zhuǎn)為核心態(tài)介陶,頻繁的阻塞和喚醒對(duì)CPU來(lái)說(shuō)是一件負(fù)擔(dān)很重的工作,勢(shì)必會(huì)給系統(tǒng)的并發(fā)性能帶來(lái)很大的壓力衬潦。同時(shí)我們發(fā)現(xiàn)在許多應(yīng)用上面斤蔓,對(duì)象鎖的鎖狀態(tài)只會(huì)持續(xù)很短一段時(shí)間植酥,為了這一段很短的時(shí)間頻繁地阻塞和喚醒線程是非常不值得的镀岛。所以引入自旋鎖。
何謂自旋鎖友驮?
所謂自旋鎖漂羊,就是讓該線程等待一段時(shí)間,不會(huì)被立即掛起卸留,看持有鎖的線程是否會(huì)很快釋放鎖走越。怎么等待呢?執(zhí)行一段無(wú)意義的循環(huán)即可(自旋)耻瑟。
自旋等待不能替代阻塞旨指,先不說(shuō)對(duì)處理器數(shù)量的要求(多核,貌似現(xiàn)在沒(méi)有單核的處理器了)喳整,雖然它可以避免線程切換帶來(lái)的開銷谆构,但是它占用了處理器的時(shí)間。如果持有鎖的線程很快就釋放了鎖框都,那么自旋的效率就非常好搬素,反之,自旋的線程就會(huì)白白消耗掉處理的資源,它不會(huì)做任何有意義的工作熬尺,典型的占著茅坑不拉屎摸屠,這樣反而會(huì)帶來(lái)性能上的浪費(fèi)。所以說(shuō)粱哼,自旋等待的時(shí)間(自旋的次數(shù))必須要有一個(gè)限度季二,如果自旋超過(guò)了定義的時(shí)間仍然沒(méi)有獲取到鎖,則應(yīng)該被掛起揭措。
自旋鎖在JDK 1.4.2中引入戒傻,默認(rèn)關(guān)閉,但是可以使用-XX:+UseSpinning開開啟蜂筹,在JDK1.6中默認(rèn)開啟需纳。同時(shí)自旋的默認(rèn)次數(shù)為10次,可以通過(guò)參數(shù)-XX:PreBlockSpin來(lái)調(diào)整艺挪;
如果通過(guò)參數(shù)-XX:preBlockSpin來(lái)調(diào)整自旋鎖的自旋次數(shù)不翩,會(huì)帶來(lái)諸多不便。假如我將參數(shù)調(diào)整為10麻裳,但是系統(tǒng)很多線程都是等你剛剛退出的時(shí)候就釋放了鎖(假如你多自旋一兩次就可以獲取鎖)口蝠,你是不是很尷尬。于是JDK1.6引入自適應(yīng)的自旋鎖津坑,讓虛擬機(jī)會(huì)變得越來(lái)越聰明妙蔗。

適應(yīng)自旋鎖

JDK 1.6引入了更加聰明的自旋鎖,即自適應(yīng)自旋鎖疆瑰。所謂自適應(yīng)就意味著自旋的次數(shù)不再是固定的眉反,它是由前一次在同一個(gè)鎖上的自旋時(shí)間及鎖的擁有者的狀態(tài)來(lái)決定。它怎么做呢穆役?線程如果自旋成功了寸五,那么下次自旋的次數(shù)會(huì)更加多,因?yàn)樘摂M機(jī)認(rèn)為既然上次成功了耿币,那么此次自旋也很有可能會(huì)再次成功梳杏,那么它就會(huì)允許自旋等待持續(xù)的次數(shù)更多。反之淹接,如果對(duì)于某個(gè)鎖十性,很少有自旋能夠成功的,那么在以后要或者這個(gè)鎖的時(shí)候自旋的次數(shù)會(huì)減少甚至省略掉自旋過(guò)程塑悼,以免浪費(fèi)處理器資源劲适。
有了自適應(yīng)自旋鎖,隨著程序運(yùn)行和性能監(jiān)控信息的不斷完善拢肆,虛擬機(jī)對(duì)程序鎖的狀況預(yù)測(cè)會(huì)越來(lái)越準(zhǔn)確减响,虛擬機(jī)會(huì)變得越來(lái)越聰明靖诗。

鎖消除

為了保證數(shù)據(jù)的完整性,我們?cè)谶M(jìn)行操作時(shí)需要對(duì)這部分操作進(jìn)行同步控制支示,但是在有些情況下刊橘,JVM檢測(cè)到不可能存在共享數(shù)據(jù)競(jìng)爭(zhēng),這是JVM會(huì)對(duì)這些同步鎖進(jìn)行鎖消除颂鸿。鎖消除的依據(jù)是逃逸分析的數(shù)據(jù)支持促绵。
如果不存在競(jìng)爭(zhēng),為什么還需要加鎖呢嘴纺?所以鎖消除可以節(jié)省毫無(wú)意義的請(qǐng)求鎖的時(shí)間败晴。變量是否逃逸,對(duì)于虛擬機(jī)來(lái)說(shuō)需要使用數(shù)據(jù)流分析來(lái)確定栽渴,但是對(duì)于我們程序員來(lái)說(shuō)這還不清楚么尖坤?我們會(huì)在明明知道不存在數(shù)據(jù)競(jìng)爭(zhēng)的代碼塊前加上同步嗎?但是有時(shí)候程序并不是我們所想的那樣闲擦?我們雖然沒(méi)有顯示使用鎖慢味,但是我們?cè)谑褂靡恍㎎DK的內(nèi)置API時(shí),如StringBuffer墅冷、Vector纯路、HashTable等,這個(gè)時(shí)候會(huì)存在隱形的加鎖操作寞忿。比如StringBuffer的append()方法驰唬,Vector的add()方法:

public void vectorTest(){
     Vector<String> vector = new Vector<String>();
     for(int i = 0 ; i < 10 ; i++){
         vector.add(i + "");
     }
 
     System.out.println(vector);
 }

在運(yùn)行這段代碼時(shí),JVM可以明顯檢測(cè)到變量vector沒(méi)有逃逸出方法vectorTest()之外腔彰,所以JVM可以大膽地將vector內(nèi)部的加鎖操作消除叫编。

鎖粗化

我們知道在使用同步鎖的時(shí)候,需要讓同步塊的作用范圍盡可能小—僅在共享數(shù)據(jù)的實(shí)際作用域中才進(jìn)行同步萍桌,這樣做的目的是為了使需要同步的操作數(shù)量盡可能縮小宵溅,如果存在鎖競(jìng)爭(zhēng),那么等待鎖的線程也能盡快拿到鎖上炎。
在大多數(shù)的情況下,上述觀點(diǎn)是正確的雏搂,LZ也一直堅(jiān)持著這個(gè)觀點(diǎn)藕施。但是如果一系列的連續(xù)加鎖解鎖操作凸郑,可能會(huì)導(dǎo)致不必要的性能損耗,所以引入鎖粗話的概念芙沥。
鎖粗話概念比較好理解浊吏,就是將多個(gè)連續(xù)的加鎖、解鎖操作連接在一起救氯,擴(kuò)展成一個(gè)范圍更大的鎖找田。如上面實(shí)例:vector每次add的時(shí)候都需要加鎖操作着憨,JVM檢測(cè)到對(duì)同一個(gè)對(duì)象(vector)連續(xù)加鎖墩衙、解鎖操作甲抖,會(huì)合并一個(gè)更大范圍的加鎖漆改、解鎖操作,即加鎖解鎖操作會(huì)移到for循環(huán)之外准谚。

三.鎖的等級(jí)
鎖主要存在四中狀態(tài)挫剑,依次是:無(wú)鎖狀態(tài)、偏向鎖狀態(tài)暮顺、輕量級(jí)鎖狀態(tài)秀存、重量級(jí)鎖狀態(tài)捶码,他們會(huì)隨著競(jìng)爭(zhēng)的激烈而逐漸升級(jí)。注意鎖可以升級(jí)不可降級(jí)惫恼,這種策略是為了提高獲得鎖和釋放鎖的效率澳盐。

偏向鎖是指一段同步代碼一直被一個(gè)線程所訪問(wèn)祈纯,那么該線程會(huì)自動(dòng)獲取鎖叼耙。降低獲取鎖的代價(jià)。其中識(shí)別是不是同一個(gè)線程一只獲取鎖的標(biāo)志是在上面提到的對(duì)象頭Mark Word(標(biāo)記字段)中存儲(chǔ)的簇爆。
輕量級(jí)鎖是指當(dāng)鎖是偏向鎖的時(shí)候,被另一個(gè)線程所訪問(wèn)入蛆,偏向鎖就會(huì)升級(jí)為輕量級(jí)鎖硕勿,其他線程會(huì)通過(guò)自旋的形式嘗試獲取鎖,不會(huì)阻塞源武,提高性能想幻。
重量級(jí)鎖是指當(dāng)鎖為輕量級(jí)鎖的時(shí)候话浇,另一個(gè)線程雖然是自旋,但自旋不會(huì)一直持續(xù)下去凳枝,當(dāng)自旋一定次數(shù)的時(shí)候,還沒(méi)有獲取到鎖岖瑰,就會(huì)進(jìn)入阻塞叛买,該鎖膨脹為重量級(jí)鎖蹋订。重量級(jí)鎖會(huì)讓其他申請(qǐng)的線程進(jìn)入阻塞,性能降低露戒。這時(shí)候也就成為了原始的Synchronized的實(shí)現(xiàn)。
JVM在運(yùn)行過(guò)程會(huì)根據(jù)實(shí)際情況對(duì)添加了Synchronized關(guān)鍵字的部分進(jìn)行鎖自動(dòng)升級(jí)來(lái)實(shí)現(xiàn)自我優(yōu)化动漾。

以上就是Synchronized的實(shí)現(xiàn)原理和java1.6以后對(duì)其所做的優(yōu)化以及在實(shí)際運(yùn)行中可能遇到的鎖升級(jí)等荠锭。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市证九,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌呀页,老刑警劉巖拥坛,帶你破解...
    沈念sama閱讀 212,718評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異渴逻,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門梨撞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人卧波,你說(shuō)我怎么就攤上這事「哿唬” “怎么了?”我有些...
    開封第一講書人閱讀 158,207評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵寸宏,是天一觀的道長(zhǎng)偿曙。 經(jīng)常有香客問(wèn)我,道長(zhǎng)望忆,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,755評(píng)論 1 284
  • 正文 為了忘掉前任稿壁,我火速辦了婚禮歉备,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘威创。我一直安慰自己,他們只是感情好溃斋,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,862評(píng)論 6 386
  • 文/花漫 我一把揭開白布吸申。 她就那樣靜靜地躺著,像睡著了一般截碴。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上走哺,一...
    開封第一講書人閱讀 50,050評(píng)論 1 291
  • 那天哲虾,我揣著相機(jī)與錄音择示,去河邊找鬼晒旅。 笑死栅盲,一個(gè)胖子當(dāng)著我的面吹牛废恋,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播拟烫,決...
    沈念sama閱讀 39,136評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼蚓哩,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了岸梨?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,882評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤半开,失蹤者是張志新(化名)和其女友劉穎赃份,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體抓韩,經(jīng)...
    沈念sama閱讀 44,330評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,651評(píng)論 2 327
  • 正文 我和宋清朗相戀三年尝江,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了英上。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,789評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡惭聂,死狀恐怖相恃,靈堂內(nèi)的尸體忽然破棺而出辜纲,到底是詐尸還是另有隱情,我是刑警寧澤屋摇,帶...
    沈念sama閱讀 34,477評(píng)論 4 333
  • 正文 年R本政府宣布幽邓,位于F島的核電站火脉,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏倦挂。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,135評(píng)論 3 317
  • 文/蒙蒙 一方援、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧送火,春花似錦先匪、人聲如沸种吸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)猖败。三九已至降允,卻和暖如春恩闻,著一層夾襖步出監(jiān)牢的瞬間拟糕,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工侠草, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人边涕。 一個(gè)月前我還...
    沈念sama閱讀 46,598評(píng)論 2 362
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像园爷,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子童社,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,697評(píng)論 2 351