偏向鎖
對一個物件的鎖偏向於某個執行緒,在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裡面的鎖的理解,希望能幫到大家,如果有錯歡迎指出一起討論!