鎖時什么?
鎖是訪問資源的憑證,它是為了保證所對象的串行訪問和安全性。
對象頭mark是什么?
描述對象的hash、鎖信息、垃圾回收標記、gc年齡等信息
-執(zhí)行鎖記錄的指針
-執(zhí)行monitor的指針
-GC標記
-偏向鎖線程id
鎖類型
偏向鎖
鎖偏向于當前已經(jīng)占有鎖的線程
將對象頭mark的標記設(shè)置為偏向,并將線程id寫入對象頭mark
在沒有競爭的條件下,獲得偏向鎖的線程,進入同步快,不需要做同步
當其他線程請求相同的鎖時,偏向模式結(jié)束
在競爭不是很激烈的情況下,可以提高性能;但是在激烈的競爭下,偏向鎖反而會增加系統(tǒng)負擔
輕量級鎖
BasicObjectLock嵌入在線程棧中的對象
快速的鎖定方法
如果對象沒有被鎖定,將對象頭的mark指針保存到鎖對象,將對象頭設(shè)置為指向鎖的指針,鎖對象BasicObjectLock位于線程棧中
如果輕量級鎖失敗,表示存在競爭,升級為重量級鎖。
自旋鎖
當存在競爭時,如果線程可以很快獲得鎖,可以讓線程做幾個空操作(自旋)
如果同步快很短,自旋成功,節(jié)省線程掛起切換時間,提高系統(tǒng)性能;如果同步快很長,自旋失敗,還是會線程掛起,會降低系統(tǒng)性能。
獲取鎖步驟:首先會嘗試獲得偏向鎖,獲取失敗會嘗試獲取輕量級鎖,獲取失敗會獲取自旋鎖,再失敗就獲取重量級鎖,在操作系統(tǒng)層掛起
鎖優(yōu)化手段
減少鎖持有時間
減小鎖粒度
鎖分離 比如說讀寫鎖、LinkedBlockingQueue(take鎖、put鎖)
鎖粗化
鎖消除
無鎖(樂觀鎖)
樂觀鎖是通過CAS(compare and swap)來實現(xiàn)非阻塞判斷。
CAS 操作中包含三個操作數(shù) —— 需要讀寫的內(nèi)存位置(V)、進行比較的預期原值(A)和擬寫入的新值(B)。如果內(nèi)存位置V的值與預期原值A(chǔ)相匹配,那么處理器會自動將該位置值更新為新值B。否則處理器不做任何操作。
CAS原理:CAS通過調(diào)用JNI(java native interface)的代碼實現(xiàn)的。而compareAndSwapInt就是借助C來調(diào)用CPU底層指令實現(xiàn)的。
CAS缺點:
- ABA問題
因為CAS需要在操作值的時候檢查下值有沒有發(fā)生變化,如果沒有發(fā)生變化則更新,但是如果一個值原來是A,變成了B,又變成了A,那么使用CAS進行檢查時會發(fā)現(xiàn)它的值沒有發(fā)生變化,但是實際上卻變化了。
解決方案:在變量前面追加上版本號,每次變量更新的時候把版本號加一,那么A-B-A 就會變成1A-2B-3A - 循環(huán)時間長開銷大
自旋CAS如果長時間不成功,會給CPU帶來非常大的開銷。自旋時會占用cpu時間片,會造成cpu的浪費。同時還會因為偏向鎖的原因,可能會出現(xiàn)線程饑餓
解決方案:引入自適應(yīng)自旋,而不是一個固定的自旋時間。 - 只能保證一個共享變量的原子操作
當對一個共享變量執(zhí)行操作時,我們可以使用循環(huán)CAS的方式來保證原子操作,但是對多個共享變量操作時,循環(huán)CAS就無法保證操作的原子性。
解決方案:從Jdk1.5開始JDK提供了AtomicReference類來保證引用對象之間的原子性,你可以把多個變量放在一個對象里來進行CAS操作
CAS與Synchronized的使用情景
- 對于資源競爭較少(線程沖突較輕)的情況,使用synchronized同步鎖進行線程阻塞和喚醒切換以及用戶態(tài)內(nèi)核態(tài)間的切換操作額外浪費消耗cpu資源;而CAS基于硬件實現(xiàn),不需要進入內(nèi)核,不需要切換線程,操作自旋幾率較少,因此可以獲得更高的性能。
- 對于資源競爭嚴重(線程沖突嚴重)的情況,CAS自旋的概率會比較大,從而浪費更多的CPU資源,效率低于synchronized。
補充: synchronized在jdk1.6之后,已經(jīng)改進優(yōu)化。synchronized的底層實現(xiàn)主要依靠Lock-Free的隊列,基本思路是自旋后阻塞,競爭切換后繼續(xù)競爭鎖,稍微犧牲了公平性,但獲得了高吞吐量。在線程沖突較少的情況下,可以獲得和CAS類似的性能;而線程沖突嚴重的情況下,性能遠高于CAS。