偏向鎖/輕量鎖/重級鎖鎖鎖更健康,上鎖解鎖到底是怎麼完成實現的,我來告訴你
一起養成寫作習慣!這是我參與「掘金日新計劃 · 4 月更文挑戰」的第7天,點選檢視活動詳情
輕量級鎖升級重量級鎖
- 只有一個執行緒搶奪時JVM上偏向鎖
- 在出現一個執行緒時就是輕量級鎖。輕量級鎖通過CAS進行上鎖。失敗則會發生自旋
- 當自旋大一定程度或者,此時又出現一個執行緒上鎖,此時會切換成重量級鎖。
class Heavy{
}
public class HeavyLock {
public static void main(String[] args) throws InterruptedException {
Heavy heavy = new Heavy();
final Thread t1 = new Thread(new Runnable() {
@SneakyThrows
@Override
public void run() {
synchronized (heavy) {
System.out.println("t1:"+ClassLayout.parseInstance(heavy).toPrintable());
}
TimeUnit.SECONDS.sleep(1000000);
}
});
final Thread t2 = new Thread(new Runnable() {
@SneakyThrows
@Override
public void run() {
synchronized (heavy) {
System.out.println("t2"+ClassLayout.parseInstance(heavy).toPrintable());
}
TimeUnit.SECONDS.sleep(1000000);
}
});
final Thread t3 = new Thread(new Runnable() {
@SneakyThrows
@Override
public void run() {
synchronized (heavy) {
System.out.println("t3"+ClassLayout.parseInstance(heavy).toPrintable());
}
TimeUnit.SECONDS.sleep(1000000);
}
});
t1.start();
TimeUnit.SECONDS.sleep(2);
t2.start();
t3.start();
TimeUnit.SECONDS.sleep(5);
System.out.println(ClassLayout.parseInstance(heavy).toPrintable());
}
}
- 我們能夠發現,在出現兩個執行緒進行輕量級鎖爭搶的時候會切換成重量級鎖。
- 都是重量級鎖的同時,我發現其他資訊是固定的,這個也和我們在記憶體佈局中一樣,這裡指向的是鎖指標,對應的是C++中ObjectMonitor物件指標。重量級指標內部有一個佇列就是將沒搶上的執行緒掛起用的。所以這裡不會變化。這裡注意和執行緒id進行區分
- 重量級鎖用完之後就會釋放掉成無鎖狀態。
鎖操作
- 上面我們分別介紹了偏向鎖,輕量級鎖,重量級鎖的定義及場景切換。這裡我們稍微總結下三種鎖使用場景
- 偏向鎖在JVM開啟偏向條件下,預設是偏向鎖
- 當有另外一個執行緒再次對物件進行加鎖時,不管有沒有發生競爭都是輕量級鎖。只不過發生競爭時會通過CAS搶佔,CAS一定次數還是失敗則升級重量級鎖。而偏向鎖切換到輕量級鎖的過程是先撤銷偏向鎖在上輕量級鎖
- 當有兩個及以上的時候發生競爭就會切換成重量級鎖。切換之前會有一個執行緒通過自旋等待。預設是10次。這是不固定的,因為有自適應自旋鎖的存在
-
除了鎖之間的關係,我們還需要掌握下各個鎖的原理,上鎖解鎖的全過程
-
偏向鎖
上鎖
- 首先我們檢測下狀態位是否是
101
。如果是在看下當前markword儲存的threadId是否是當前執行緒。如果是當前執行緒則可以執行當前程式碼塊 - 如果不是當前執行緒,則通過CAS寫入當前執行緒;如果寫入成功則說明偏向鎖上鎖成功,繼續執行當前程式碼塊
- 如果CAS寫入失敗,則說明有資源搶佔。偏向鎖升級成輕量級鎖。
- 首先我們檢測下狀態位是否是
### 解鎖
- 上面的程式碼案列中,我也有所提到偏向鎖是不會主動釋放的。因為我們在還沒加鎖的時候預設就是偏向鎖。只有發生競爭了升級輕量級鎖的時候才會撤銷偏向鎖。
- 所以說偏向鎖的解鎖就是不操作。我們這裡主要說下在升級輕量級鎖是關於偏向鎖的撤銷的邏輯
## 輕量級鎖
### 上鎖
- 輕量級鎖上鎖過程需要藉助一個`Lock Record` ; 他是儲存線上程棧幀中的一塊記憶體地址。
- 因為偏向鎖不需要釋放,他的可重入式鎖就是不做任何操作。但是在輕量級鎖中涉及到釋放鎖。那麼輕量級鎖如何體現可重入式呢?[這裡參考下這篇文章](https://dandelioncloud.cn/article/details/1403089140002131970);
- 在上圖中如果是無鎖或者偏向鎖會線上程棧中開闢LockRecord來儲存markword的地址叫做`Displaced MardWord`
- 但是在已經是偏向鎖的條件分支裡,我沒有體現可重入式的概念。在這條分支線裡實際上是檢測到markword中指向的是當前棧幀的時候JVM還是會開闢一個`Lock Record`,也不會寫回到markword中。`LockRecord`不會儲存原本markword的內容。此時的`LockRecord`本身就是一個計數器的功能。在釋放重入式鎖的時候也只是刪除`LockRecord`而不會去操作markword。
- 為什麼JVM這樣設計呢?因為這樣就避免了每次輕量級鎖的開銷
### 解鎖
- 對於輕量級鎖,其效能提升的依據是“對於絕大部分的鎖,在整個生命週期內都是不會存在競爭的”,如果打破這個依據則除了互斥的開銷外,還有額外的CAS操作,因此在有多執行緒競爭的情況下,輕量級鎖比重量級鎖更慢;
總結
synchronized
作為被人詬病的一個設計,在JDK1.6之後真的改版了,不要在拿之前的眼光看待他了。畢竟偏向鎖和輕量級鎖的引入已經將效能提升了很多了。- 偏向鎖預設進行,節省排程時間
- 輕量級鎖通過CAS完成等待,省調執行緒掛起,喚醒等操作
- 重量級將執行緒序列化,保障了執行緒之間的併發
- 三種狀態鎖循序漸進給我們適配不同程度的併發需求
- 避免回表,引入索引下推|提高索引命中率 | 提前下班啦
- TDengine 時序性資料庫為什麼海量資料下不卡頓呢
- 神奇的XPath,快速完成前端及XML的元素定位,茫茫大海不迷路
- springboot通用分支處理---還在硬編碼特殊處理邏輯?超級管理員不應該被區別對待
- Spring事務太強大了,相容資料庫同時給我們提供多種組合應對業務需求
- java物件在記憶體中如何分佈 | java上鎖原來就是記憶體佔位,so easy
- linux三劍客之編輯器sed出廠
- linux三劍客awk教你如何裁剪結果集
- 執行緒池7個引數拿捏死死的,完爆面試官
- 執行緒池存在的意義
- 多年程式設計師總結下來的懶人必備指令碼之進度條⚠️製作
- java中的static關鍵字說清楚還得靠JVM
- 設計模式存在哪些關聯關係,六種關係傻傻分不清--- UML圖示詳解
- 每次需求評審產品總是讓我提高程式碼複用,說白了就是合成複用原則
- 越級上報不可行,各司其職才是王道---迪米特法則
- 偏向鎖/輕量鎖/重級鎖鎖鎖更健康,上鎖解鎖到底是怎麼完成實現的,我來告訴你
- 狸貓換太子里氏替換原則;不要一味的進行抽象否則最後你無法hold你的物件
- 設計模式是我擺脫碼畜的唯一出路---依賴倒轉原則
- 學好數理化,寫遍所有程式碼都不怕,我用數學分類討論的思想解決
- synchronized已經不在臃腫了,放下對他的成見之初識輕量級鎖