大白話講Java的鎖
- 2022 年 5 月 26 日
- 筆記
偏向鎖
對一個對象的鎖偏向於某個執行緒,在markword中記錄執行緒id
下次相同的執行緒來,直接就可以獲取鎖
輕量級鎖
對象的Markword記錄鎖地址 跟執行緒棧裡面的鎖記錄Lock Record的鎖地址進行交換
重入鎖
什麼是重入鎖 這裡舉個程式碼例子
Thread t1 = new Thread(()->{
synchronized(this){
add();
}
}).start();
private synchronized void add(){
xxx;
}
首先你看 執行緒t1里 synchronized(this)
獲取了鎖,他調用了add
方法,但是add
方法也需要當前對象的鎖吧
正常情況下鎖被上面的程式碼拿了,裡面的add是不是不能獲取鎖,會卡住形成死鎖?
這時候重入鎖就可以解決這個問題
第一次獲取鎖的時候 Lock Record裡面的鎖地址索引跟 Object的MarkWord裡面的鎖地址進行交換
第二次要獲取這個鎖 也就是所重入 他發現Object裡面的鎖地址就是當前執行緒,於是Thread-0就要重入鎖 創一個null的鎖記錄 代表這次是重入鎖
解鎖的時候按順序一個一個解鎖 從null開始 到下面那個鎖解鎖
重量級鎖
在輕量級鎖的基礎上,如果鎖膨脹,就會變成重量級鎖
首先執行緒0 Thread-0想要獲取對象Object的鎖
Thread-0創建自己的鎖記錄 把鎖記錄的鎖地址跟Object的鎖地址進行交換,現在鎖記錄的鎖地址是「無鎖」,Object對象裡面的鎖地址是輕量級鎖
現在執行緒1也想要獲取Object的鎖 但是注意 現在Object的鎖被執行緒0拿了
執行緒1看了一眼Object裡面的鎖地址 怎麼是個輕量級鎖 怎麼不是無鎖
這時候 發生了鎖的競爭!
就需要鎖膨脹,膨脹成重量級鎖
重量級鎖要用到作業系統裡面的鎖對象 Monitor對象
接下來步驟是這樣的
1 Monitor的Owner(鎖主人)指向Thread-0 (因為剛剛Object的鎖被Thread-0拿了)
2 Thread-1加入到EntryList裡面,進行阻塞。
3 等到Thread-0用完了這個鎖,把鎖示釋放開了,Owner指向清空,現在鎖沒有主人了
4 Thread-0喚醒EntryList裡面的阻塞的Thread-1
5 Thread-1獲得鎖
完成
自旋鎖
剛剛的重量級鎖,Thread-1是加入到entry list裡面去等執行緒0示範鎖,進去就阻塞對吧?
現在可以優化一下,進去entry list先別急著擺爛阻塞,你先試試。
於是Thread1開始自旋,好了沒好了沒好了沒
哦?Thread0好了?那就直接把鎖拿到。
為什麼這樣比較快?因為阻塞會有執行緒上下文切換,開銷很大的
JVM現在很牛。會優化自旋鎖,如果前面自旋經常能獲取鎖,就更願意讓他自旋。如果好幾次自旋根本就沒用,JVM就會減少自旋次數甚至不自旋,具體的演算法我也不懂,反正很智慧
批量重偏向
基於偏向鎖
這個偏向鎖偏向執行緒1 ,這時候執行緒1用完了鎖,並且以後也不怎麼用了。
現在執行緒2要用這個鎖,並且沒有執行緒跟他競爭(注意!沒有競爭,有競爭不就膨脹成重量級鎖了嗎)
這時候要把原來偏向於執行緒1的鎖改掉,改成偏向執行緒2 這就是鎖的重偏向
那麼為什麼說 批量重偏向,批量是什麼意思?
首先批量重偏向是以一個類為單位(一個Class為單位)所有示例對象都算這個Class
比如一個Dog類 有很多實例對象 dog1 dog2 dog3 dog4 dog5
這裡有個批量重偏向閾值20
本來所有Dog的示例dog1234567都是偏向執行緒1的
現在執行緒2要用這些狗實例,慢慢的dog1從偏向執行緒1變成偏向執行緒2,dog2也偏向執行緒2,dog3也變,dog4也變
如此變了20次(閾值)的時候,jvm覺得,咋回事,偏向鎖的出現本來不是為了加快效率嗎,你這樣一直變變變,不是反而慢了嗎
ok,既然這些狗對象一直變成偏向執行緒2,那就統統給我偏向到執行緒2。比如你創建了40條狗,二十條狗被你從偏向執行緒1改成偏向執行緒二,就觸發了批量重偏向,現在所有的狗都偏向於執行緒二了
、
批量撤銷
ok 批量沖偏向已經很好用了吧 現在我再來個新閾值 叫做批量撤銷閾值:40 剛剛觸發20次的時候會觸發批量重偏向,讓所有的狗都去執行緒二,偏向鎖還是可以用的。
現在撤銷了四十次,JVM就覺得,搞毛,一直撤銷,都四十次了,這樣效率很低。化身惡魔,都別用。
40次的撤銷觸發了批量撤銷後,所有的DOG的實例(dog1,dog2,dog345678)統統變成不可偏向!
不僅以前創建的變成不可偏向,新創建的小狗實例也不準用偏向
OK 這就是我對java裡面的鎖的理解,希望能幫到大家,如果有錯歡迎指出一起討論!