初識
synchronized 可以加在方法和類上面,作用于類和對象。下面代碼中列出了 synchronized 的用法。
public class SynchronizedTest {
public static final Object lock = new Object();
// 鎖的是SynchronizedTest.class對象
public static synchronized void sync1() {
}
// 鎖的是SynchronizedTest.class對象
public static void sync2() {
synchronized (SynchronizedTest.class) {
}
}
// 鎖的是當前實例this
public synchronized void sync3() {
}
// 鎖的是當前實例this
public void sync4() {
synchronized (this) {
}
}
// 鎖的是指定對象lock
public void sync5() {
synchronized (lock) {
}
}
}
synchronized 大家都知道是用 monitorenter 和 monitorexit 兩個指令鎖住同步塊的。
那么 synchronized 是怎么膨脹的呢?為什么會膨脹呢?
先從 JVM 內存開始講起,對象在被實例化后,是存放在堆內存中的,它由 3 部分組成:
- 對象頭:存放對象運行時的狀態的信息、指向該對象所屬 Class 的元數據的指針。
- 實例數據:存放對象的屬性數據信息,包括父類的信息。
- 對齊填充字節:由于虛擬機要求對象的大小必須是 8 字節的整數倍。不是必須存在,僅僅是為了字節對齊。
其中對象頭里面包含了 Mark Word(標記字段)和 Class Pointer(類型指針)
- Mark Word 默認的存儲對象的 hashcode、分代年齡、是否偏向鎖、鎖標識位的信息,它在運行期間的存儲內容會隨著鎖的變化而變化。
Mark Word (32 bits) | 是否偏向鎖 | 鎖標識位值 | 鎖狀態 |
---|---|---|---|
對象的hashcode(25)、分代年齡(4)、是否偏向鎖(1)、鎖標識位(2) | 0 | 01 | 無鎖 |
線程ID(23)、偏向時間戳(2)、分代年齡(4)、是否偏向鎖(1)、鎖標識位(2) | 1 | 01 | 偏向鎖 |
指向棧中鎖記錄的指針(30)、鎖標識位(2) | 00 | 輕量級鎖 | |
指向重量級鎖的指針(30)、鎖標識位(2) | 10 | 重量級鎖 |
- Class Pointer(類型指針):對象指向類的元數據的指針,虛擬機通過這個指針來確定對象是哪一個類的實例。
鎖膨脹
偏向鎖、輕量級鎖、重量級鎖、自旋鎖,這些都是Synchronzied的鎖的實現。Synchrozied會根據不同的場景選擇不同的鎖,我們只使用Synchronzied,不用關心它具體使用的哪個鎖。
偏向鎖
在java 程序中,大多數情況不存在多個線程同時競爭鎖,往往都是同一個線程多次獲得同一個鎖。
當只有一個線程在競爭鎖的時候,在線程獲取到鎖后,將進入偏向模式,程序會將對象的頭的前 23 個字節用 CAS 的方式存儲線程 ID。下次有線程競爭鎖,只需要比較對象頭中的線程 ID 是不是和此時獲取到鎖的線程 ID 相同。如果相同線程就直接進入同步代碼塊,不需要 CAS 競爭鎖。
有另外的線程在競爭鎖的時候,持有偏向鎖的線程才會釋放鎖,持有偏向鎖的線程不會主動釋放偏向鎖。偏向鎖的撤銷,是在沒有字節碼執行的時候進行的。首先會暫停偏向鎖的線程,判斷鎖對象是否被鎖住。撤銷偏向鎖后恢復成無鎖或者是輕量級鎖。
輕量級鎖
當有另外的線程在競爭偏向鎖的時候并且競爭失敗了,偏向鎖就會膨脹為輕量級鎖,其他的線程會通過自旋的方式嘗試獲取鎖。
JVM 會在當前線程的棧幀中創建一個叫做鎖記錄(Lock Record)的空間,將鎖對象的 Mark Word 復制進去。這個官方稱為 Displaced Mard Word。然后 JVM 將使用 CAS 操作嘗試將鎖對象的Mark Word 更新為指向 Lock Record 的指針。如果更新成功,鎖標識位就成為 00,此時為輕量級鎖。
重量級鎖
從上面的表格中就指出重量級鎖的對象頭里面存儲的是指向 monitor 的指針,那 monitor 是什么呢?
monitor 又稱為管程,Java 中由 ObjectMonitor 實現。當線程要將對象加鎖的時候,對象會創建一個monitor。
ObjectMonitor 主要的字段有:
- owner:就是當前加鎖的線程
- waitSet:就是 owner的線程調用了 wait() 方法,就進入這個里面
- entryList:加鎖失敗的線程阻塞在這個里面
- recursions:鎖的重入次數
- count:用來記錄是不是有對象加鎖:0.當前對象沒有線程加鎖,1. 當前對象有線程加鎖
從輕量級鎖升級到重量級鎖的時候,對象頭 Mark Word 存儲已經變成了指向 Monitor 的指針。線程可以通過這個指針找到 ObjectMonitor,放入 entryList 等待重量級鎖釋放后競爭。entryList 中的線程 CAS 嘗試更新 count = 1,當更新成功后將 owner 設置為當前的線程。當 owner 的線程調用了 wait() 方法,線程就會釋放鎖,進入 waitSet 中。這個時候 count = 1,owner = null,entryList 的線程可以再次競爭鎖。
總結
- synchronized 不管是加在類上還是方法上,如果作用在類上,這個類的所有對象都是同一把鎖,
- 鎖膨脹時不可以降級的
-
內存
+關注
關注
8文章
3019瀏覽量
74007 -
代碼
+關注
關注
30文章
4780瀏覽量
68527 -
JVM
+關注
關注
0文章
158瀏覽量
12220
發布評論請先 登錄
相關推薦
評論