大家知道,Java的多線程安全是基于Lock機(jī)制實(shí)現(xiàn)的,而Lock的性能往往不如人意。
原因是,monitorenter與monitorexit這兩個(gè)控制多線程同步的bytecode原語,是JVM依賴操作系統(tǒng)互斥(mutex)來實(shí)現(xiàn)的。
互斥是一種會(huì)導(dǎo)致線程掛起,并在較短的時(shí)間內(nèi)又需要重新調(diào)度回原線程的,較為消耗資源的操作。
為了優(yōu)化Java的Lock機(jī)制,從Java6開始引入了輕量級(jí)鎖的概念。
輕量級(jí)鎖(Lightweight Locking)本意是為了減少多線程進(jìn)入互斥的幾率,并不是要替代互斥。
它利用了CPU原語Compare-And-Swap(CAS,匯編指令CMPXCHG),嘗試在進(jìn)入互斥前,進(jìn)行補(bǔ)救。
本文將詳細(xì)介紹JVM如何利用CAS,實(shí)現(xiàn)輕量級(jí)鎖。
原理詳解
Java Object Model中定義,Object Header是一個(gè)2字(1 word = 4 byte)長度的存儲(chǔ)區(qū)域。
第一個(gè)字長度的區(qū)域用來標(biāo)記同步,GC以及hash code等,官方稱之為 mark word。第二個(gè)字長度的區(qū)域是指向到對(duì)象的Class。
在2個(gè)word中,mark word是輕量級(jí)鎖實(shí)現(xiàn)的關(guān)鍵。它的結(jié)構(gòu)見下表

從表中可以看到,state為lightweight locked的那行即為輕量級(jí)鎖標(biāo)記。bitfieds名為指向lock record的指針,這里的lock record,其實(shí)是一塊分配在線程堆棧上的空間區(qū)域。
用于CAS前,拷貝object上的mark word(為什么要拷貝,請(qǐng)看下文)。
第三項(xiàng)是重量級(jí)鎖標(biāo)記。后面的狀態(tài)單詞很有趣,inflated,譯為膨脹,在這里意思其實(shí)是鎖已升級(jí)到OS-level。
在本文的范圍內(nèi),我們只關(guān)注第二和第三項(xiàng)即可。
為了能直觀的理解lock,unlock與mark word之間的聯(lián)系,我畫了一張流程圖:

在圖中,提到了拷貝object mark word,由于脫離了原始mark word,官方將它冠以displaced前綴,即displaced mark word(置換標(biāo)記字)。
這個(gè)displaced mark word是整個(gè)輕量級(jí)鎖實(shí)現(xiàn)的關(guān)鍵,在CAS中的compare就需要用它作為條件。
為什么要拷貝mark word?
其實(shí)很簡單,原因是為了不想在lock與unlock這種底層操作上再加同步。
在拷貝完object mark word之后,JVM做了一步交換指針的操作,即流程中第一個(gè)橙色矩形框內(nèi)容所述。
將object mark word里的輕量級(jí)鎖指針指向lock record所在的stack指針,作用是讓其他線程知道,該object monitor已被占用。
lock record里的owner指針指向object mark word的作用是為了在接下里的運(yùn)行過程中,識(shí)別哪個(gè)對(duì)象被鎖住了。
下圖直觀地描述了交換指針的操作。


最后一步unlock中,我們發(fā)現(xiàn),JVM同樣使用了CAS來驗(yàn)證object mark word在持有鎖到釋放鎖之間,有無被其他線程訪問。
如果其他線程在持有鎖這段時(shí)間里,嘗試獲取過鎖,則可能自身被掛起,而mark word的重量級(jí)鎖指針也會(huì)被相應(yīng)修改。
此時(shí),unlock后就需要喚醒被掛起的線程。
原文鏈接:http://kenwublog.com/theory-of-lightweight-locking-upon-cas