講講 Synchronized/ 講講 Synchronized 鎖更新
内部實作 Markword
synchronized在修飾方法和代碼塊在位元組碼上實作方式有很大差異,但是内部實作還是基于對象頭的MarkWord來實作的。
jdk5 以前 ---重量級鎖
synchronized 隻有重量級鎖,Synchronized是通過對象内部的一個叫做 螢幕鎖 (Monitor)來實作的。
但是 螢幕鎖本質又是依賴于底層的作業系統的 互斥鎖 Mutex Lock來實作的。
并且 作業系統實作線程之間的切換這就需要從使用者态轉換到核心态,這個成本非常高,
狀态之間的轉換需要相對比較長的時間,這就是為什麼Synchronized效率低`的原因。
是以,這種依賴于作業系統的互斥鎖 Mutex Lock實作的鎖我們稱之為 "重量級鎖"。
JDK6 開始 ---偏向鎖、輕量鎖
對synchronized 進行優化,增加了自适應的 CAS自旋、鎖消除、鎖膨脹、
偏向鎖
、
輕量級鎖
這些優化政策。
鎖可以從偏向鎖更新到輕量級鎖,再更新的重量級鎖。但是鎖的更新是單向的,也就是說隻能從低到高更新
在 JDK 1.6 中預設是開啟偏向鎖和輕量級鎖的,可以通過-XX:-UseBiasedLocking來禁 用偏向鎖。
偏向鎖
偏向鎖的引進,因為HotSpot作者經過研究實踐發現,
在大多數的情況下,鎖不僅不存在多線程競争,而且總是由同一線程多次獲得,為了讓線程獲得鎖的代價更低,引進了偏向鎖。
線上程持有偏向鎖之後,後續對同步代碼塊的通路都不需要擷取鎖、釋放鎖。
偏向鎖是在單線程執行代碼塊時使用的機制,如果在多線程并發的環境下(即線程A 尚未執行完同步代碼塊,線程B發起了申請鎖的申請),則一定會轉化為輕量級鎖或者重量級鎖。
"偏向"的意思是,偏向鎖假定将來隻有第一個申請鎖的線程會使用鎖(不會有任何線程再來申請鎖),
是以,隻需要在Mark Word中CAS記錄owner(本質上也是更新,但初始值為空),
如果記錄成功,則偏向鎖擷取成功,記錄鎖狀态為偏向鎖,以後目前線程等于owner就可以零成本的直接獲得鎖;
否則,說明有其他線程競争,膨脹為輕量級鎖。
輕量鎖
引入輕量級鎖的目的其實是為了避免使用重量級鎖。
通過CAS 自旋,避免了短時間内對線程進行阻塞、喚醒,因為線程的阻塞、喚醒對應着作業系統的使用者态和核心态的切換,進而節省資源,提高程式運作的性能。
是以說,各種鎖并不是互相代替的,而是在不同場景下的不同選擇
- 偏向鎖:無實際競争,且将來隻有第一個申請鎖的線程會使用鎖。
- 輕量級鎖:無實際競争,多個線程交替使用鎖;允許短時間的鎖競争。
- 重量級鎖:有實際競争,且激烈競争
synchronized鎖更新過程總結:一句話,就是先自旋,不行再阻塞。
其他必備知識
CAS 自旋
// 執行一個無意義的循環,目的就是等代機會去競争到鎖
while(true){
//空的
//一個:每次自旋的時間
//另外一個:自旋的次數
}
自旋的作用:
避免了短時間内對線程進行阻塞、喚醒,因為線程的阻塞、喚醒對應着作業系統的使用者态和核心态的切換,進而節省資源,提高程式運作的性能。
鎖資訊存放
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiI0gTMx81dsQWZ4lmZf1GLlpXazVmcvwFciV2dsQXYtJ3bm9CX9s2RkBnVHFmb1clWvB3MaVnRtp1XlBXe0xCMy81dvRWYoNHLwEzX5xCMx8FesU2cfdGLwMzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cmbw5CM3IjN3ADN4IjZwQWOyczMzYzX4ITOzEDMxAzLcBTMyIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjLyM3Lc9CX6MHc0RHaiojIsJye.png)
鎖的資訊是存放在對象頭的 Mark word 中,Mark word 根據鎖的狀态,存儲對應的鎖的資訊
synchronized 與 鎖更新:由對象頭中的 Mark Word 根據 鎖标志位 的不同而被複用及鎖更新政策。
- 偏向鎖:MarkWord 存儲的是偏向的線程ID;
- 輕量鎖:MarkWord 存儲的是指向線程
中Lock Record的指針;棧
- 重量鎖:MarkWord 存儲的是指向
中的 monitor對象的指針;堆
鎖的狀态
無鎖狀态
偏向鎖狀态
輕量級鎖狀态
重量級鎖狀态
各種鎖的優點、缺點
阿裡巴巴開發手冊
【強制】高并發時,同步調用應該去考量
鎖的性能損耗
能用無鎖資料結構,就不要用鎖;能鎖區塊,就不要鎖整個方法體;能用對象鎖,就不要用類鎖。
說明:盡可能使加鎖的代碼塊工作量盡可能的小,避免在鎖代碼塊中調用RPC方法。
常見面試題
1、你提到了synchronized 的優化,詳細說一下 偏向鎖、輕量級鎖有什麼差別?
CAS次數不同、是否主動釋放鎖
輕量級鎖每次申請、釋放鎖都至少需要一次CAS,而偏向鎖隻有初始化時需要一次CAS.
偏向鎖,隻偏向于第一個通路的線程。在這個線程(線程A)第一次來通路同步塊時,會使用CAS,更新對象頭的ThreadID為偏向線程的id。後續的通路,隻需要比較threadId是否相同,不需要CAS操作。且這個偏向的線程是不會主動十分鎖的,除非出現線程來競争。
當第二個線程(線程B)過來通路時,他并不知第一個線程已經存在。是以這第二個線程以為它是被偏愛的,它也想向偏向線程那樣使用CAS初始化對象頭的ThreadID,發現失敗了。
說明對象鎖已經被其他線程占用,出現線程競争。
檢查原來持有該對象鎖的線程是否依然存活,
如果挂了,則可以将對象變為無鎖狀态,然後重新偏向新的線程。
如果線程還存活,則檢查線程是否在執行同步代碼塊中的代碼,如果是,則更新為輕量級鎖,進行CAS競争鎖。
輕量級鎖的CAS 是每次申請鎖都需要執行的。
2、你平時是怎麼使用 synchronized 關鍵字
(1) 修飾執行個體方法: 給目前對象執行個體加鎖
synchronized void method() {
//業務代碼
}
(2) 修飾靜态⽅法: 也就是給目前類加鎖
synchronized void staic method() {
//業務代碼
}
(3) 修飾代碼塊
synchronized(this|object|xx.class) {
//業務代碼
}
單例模式的雙重檢索也是使用單例
/* 雙重檢驗鎖 */
public class Singleton{
private Singleton(){}//構造器私有化,防止new,導緻多個執行個體
private static volatile Singleton singleton;
public static Singleton getInstance(){//向外暴露一個靜态的公共方法 getInstance
//第一層檢查
if(singleton == null){
//同步代碼塊
synchronized (Singleton.class){
//第二層檢查
if(singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}