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