Java—5 內建鎖優化
所謂內建鎖的優化就是優化線程的等待時間
CAS機制
1.CAS:全稱 Compare And Swap 比較交換機制
CAS是一種樂觀鎖機制:
悲觀鎖:在任意時刻都有線程競爭鎖,獲取鎖成功的線程會阻塞獲取鎖失敗的線程成功獲取鎖。
樂觀鎖(lock):假設所有線程訪問共享資源均不會發生衝突,既然不會發生衝突,就不會阻塞其他線程。不會阻塞線程
CAS(無鎖操作):有三個值V(內存實際存放的值),O(預期值),N更新後的值。
當V==O,說明上次該線程操作後沒有別的線程再修改值,因此可以將N存入V中;
當V!=O,說明上次該線程操作後,有其他線程修改了V值,因此不能講N替換到內存中,返回V值,並且不斷嘗試修改V值。
JDK1.6之前的內建鎖最大的問題是,每當有線程競爭鎖失敗就會將線程阻塞,這樣阻塞和喚起線程消耗了大量的資源。
CAS不是武斷的將競爭鎖失敗的線程掛起來,而是會讓失敗的線程自旋嘗試獲取鎖,到了一定次數再將線程掛起。
具體策略:如果上次自旋獲取到鎖,這次 自旋時間就長一些,否則,此次自旋時間就短一些。
CAS的問題
ABA問題:線程1 A 線程2 A—>B 線程3 B—>A 線程1 : ???
理論上來說 線程1是可以修改的,但是如果修改了,線程3就會癱瘓。
解決思路:數據庫的樂觀鎖機制,加入一個版本號: 包java.util.concurrent.atomic的 AtomicReference類
公平性問題:處於自旋狀態的線程會比阻塞狀態的線程更容易獲取鎖。
在同步代碼塊中,獲得對象monitor監視器就是獲得了對象的鎖。無非就是對象的鎖就是對象頭裏的一個標記
偏向鎖
HotSpot的作者發現,在大多數情況下,線程時不存在競爭的,爲了降低 阻塞和喚醒線程的代價引入了偏向鎖
在任意時刻只有一個線程持有鎖。
偏向鎖的獲取過程
1.一個線程訪問同步代碼塊並獲取鎖時時,會在對象頭和棧幀中存儲鎖偏向的線程ID,先看對象頭持有的線程ID是否爲自己的線程ID,如果是直接訪問同步代碼塊。
2.如果不是,查看偏向鎖字段,如果爲0,說明對象鎖還沒有被任何線程獲得,將偏向鎖字段改爲1,並且將對象頭的線程ID改成自己的。
3,如果爲1,說明對象鎖已經被別的線程獲得,當前線程不斷自旋獲取對象鎖或升級成輕量級鎖。(後者可能性較大)。一般來說 偏向鎖每自旋一次 mark word中的 Epoch就加1 當 Epoch=40時,說明該鎖已經不適合當做偏向鎖。升級爲輕量級鎖
偏向鎖的釋放:
偏向鎖的釋放代價較大,通常要到達全局安全點(CPU上沒有執行有用字節碼)纔可以釋放
偏向鎖鎖競爭,鎖升級
輕量級鎖
在不同時間,多個線程請求一把鎖。
加鎖過程:
1.當線程訪問同步代碼塊並加鎖時,JVM會在線程中開闢存放鎖記錄的棧幀,將對象頭的mark word複製到鎖記錄中 稱爲 displace mark word。
2.使用CAS將對象頭中的Mark word 替換成指向鎖記錄的指針。
3.如果成功說明,當前成功獲取鎖,如果失敗說明當前有其他線程競爭鎖,不斷自旋嘗試獲取鎖。
解鎖:
1.將鎖記錄中的 Displace Mark Word替換會 Mark Word 。
2.如果成功,成功釋放鎖,如果失敗,說明有其他線程競爭鎖,鎖直接膨脹成重量級鎖
輕量級鎖競爭(一存在競爭就升級)
總結
**重量級鎖:**採用自適應自旋來獲取鎖,避免了對於極小的同步代碼塊還存在阻塞問題。同一時間多個線程競爭鎖。
**輕量級鎖:**採用CAS獲取鎖,將鎖對象替換成指針,指向當前線程棧上的一塊空間,存儲着鎖對象原本的標記字段。它針對的是多個線程在不同時間申請一把鎖。
**偏向鎖:**偏向鎖只會在第一次請求時採用CAS操作。在鎖對象的標記中記錄下當前線程的地址。在之後的運行中只要簡單比較一下對象頭是否持有線程ID,如果測試成功,就會直接引用同步代碼塊。針對鎖僅會被同一塊線程持有。