synchronized
synchronized加到代碼塊上時兩種情況
-
synchronized(this):表示加鎖的效果如同加到普通方法上 synchronized(this){} = viod synchronized get(){} ;對象鎖:不跨線程保護
-
synchronized(Test.class):表示加鎖的效果如同加到靜態方法上 synchronized(this){} = static viod synchronized get(){} ;類鎖:跨線程保護
並發:資源共享且互斥
為什么synchronized能夠起到阻塞的效果
synchronized(lock) 中被鎖定的對象的內存布局
jdk1.6 以后 synchronized的功能進行了優化:對鎖的升級規則(無鎖->偏向鎖(cas)-》輕量級鎖(自旋)-》重量級鎖(阻塞))
鎖升級的規則
假如存在線程1、線程2 兩個線程 :
-
只有線程1訪問-- 》偏向鎖 (cas 添加線程id)
-
線程1和線程2交替訪問 --》輕量級鎖-自旋(不是鎖,只是輪詢cas更換對象的MarkWord)
-
兩者同時訪問-》持鎖時間較長(業務處理慢)-》重量級鎖
無鎖--》偏向鎖
偏向鎖--》輕量級鎖
wait/notify
線程獲取鎖 通過monitorenter(指令)成功后獲得對象鎖,其他線程進入同步隊列,wait的線程釋放鎖進入等待隊列
可見性問題
CPU層面的高速緩存帶來緩存不一致問題--》可見性問題:
cpu層面解決可見性問題 引入了:
總線鎖;緩存鎖
緩存一致性協議(x86:MESI):表示緩存行的四種狀態(會出現指令重排序:亂序執行)
總結:cpu層面仍然會存在可見性問題(但是提供內存屏障指令)
讀屏障、寫屏障、全屏障
對象頭mark word
我們可以將上面的注釋轉成以下的表格
|-----------------------------------------------------------------------------------------------------------------|
| Object Header(128bits) |
|-----------------------------------------------------------------------------------------------------------------|
| Mark Word(64bits) | Klass Word(64bits) | State |
|-----------------------------------------------------------------------------------------------------------------|
| unused:25|identity_hashcode:31|unused:1|age:4|biase_lock:1|lock:2 | OOP to metadata object | Nomal |
|-----------------------------------------------------------------------------------------------------------------|
| thread:54| epoch:2 |unused:1|age:4|biase_lock:1|lock:2 | OOP to metadata object | Biased |
|-----------------------------------------------------------------------------------------------------------------|
| ptr_to_lock_record:62 |lock:2 | OOP to metadata object | Lightweight Locked |
|-----------------------------------------------------------------------------------------------------------------|
| ptr_to_heavyweight_monitor:62 |lock:2 | OOP to metadata object | Heavyweight Locked |
|-----------------------------------------------------------------------------------------------------------------|
| |lock:2 | OOP to metadata object | Marked for GC |
|-----------------------------------------------------------------------------------------------------------------|
從上面的表格,我們可以看出Java的對象頭在對象的不同的狀態下會有不同的表現形式,主要有三種狀態,無鎖狀態,加鎖狀態,GC標記狀態。那么就可以理解Java當中的上鎖其實可以理解給對象上鎖。也就是改變對象頭的狀態,如果上鎖成功則進入同步代碼塊。但是Java當中的鎖又分為很多種,從上圖可以看出大體分為偏向鎖、輕量鎖、重量鎖三種鎖狀態。這三種鎖的效率是完全不同、關於效率的分析會在下文分析。我們需要查看對象頭,就需要用到借助JOL工具。
首先我們在項目中引入JOL的依賴,具體如下圖:
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.10</version>
</dependency>
然后創建A.java
public class A{} 然后創建JOLExample1.java
import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.vm.VM;
import static java.lang.System.out;
public class JOLExample1 {
static A a;
public static void main(String[] args) {
a = new A();
//打印JVM的詳細信息
out.println(VM.current().details());
//打印對應的對象頭信息
out.println(ClassLayout.parseInstance(a).toPrintable());
}
}
運行的結果如下
不是說Klass是64bits(8個字節)但是這兒只有4個字節,是因為我們開啟了指針壓縮,我們可以關閉指針壓縮看看,是不是8個字節。我們只需要使用以下的JVM運行參數
-XX:-UseCompressedOops
再次運行剛才的程序,可以看到我們Klass對象是64bits(16個字節),具體如下圖
看完了對象的實例數據,我們就來到了今天的重頭戲,Java的對象頭(在開啟JVM指針壓縮的情況下是12B),那么這12B存儲的是什么?我們可以看下 OpenJDK的官網的解釋
首先引用openjdk文檔當中對對象頭的解釋
上述引用中提到一個java對象頭包含2個word,並且包含了堆對象的布局、類型、GC狀態、同步狀態和標識哈希碼,具體怎么包含的呢?又是哪兩個word呢?
Mark word為第一個word根據文檔可以知道它里面包含了鎖的信息、hashcode、gc信息等等,第二個word是什么呢?
klass word 為對象頭的第二個word主要指向對象的元數據。
假設我們理解一個對象頭主要由上圖兩個部分組成(數組對象除外,數組對象的對象還包含一個數組長度),由我們的推導出Mark word是8個字節,klass word(開啟指針壓縮的情況下是4個字節,不開啟的時候是8個字節)。我們打印出來的對象頭是12個字節,所以其中的8個字節是Mark word,剩下的4個字節是klass word,但是和鎖相關的就是Mark word,那么接下來要重點分析Mark word里面信息。
由最開始的64位的表格,我們可以得知在無鎖的情況下Markword當中前56bit存的是對象的hashcode,我們來驗證一下
修改A.java 的代碼如下
import org.openjdk.jol.info.ClassLayout;
import static java.lang.System.out;
public class JOLExample2 {
public class A {
//占一個字節的boolean字段
private boolean flag;
}
新建一個JOLExample2.java具體代碼如下
import org.openjdk.jol.info.ClassLayout;
import static java.lang.System.out;
public class JOLExample2 {
public static void main(String[] args) {
A a = new A();
//沒有計算HashCode之前的對象頭
out.println("before hash");
out.println(ClassLayout.parseInstance(a).toPrintable());
//jvm計算HashCode
out.println("jvm----------" + Integer.toHexString(a.hashCode()));
//當計算完HashCode之后,我們可以查看對象頭的信息變化
out.println("after hash");
out.println(ClassLayout.parseInstance(a).toPrintable());
}
}
運行的結果如下:
可以看到我們在沒有進行hashcode運算的時候,所有的值都是空的。當我們計算完了hashcode,對象頭就是有了數據。因為是小端存儲,所以你看的值是倒過來的。前25bit沒有使用所以都是0,后面31bit存的hashcode,所以第一個字節中八位存儲的分別就是分代年齡、偏向鎖信息、對象狀態,這8bit分別表示的信息如下圖所示,這個圖會隨着對象的狀態改變而改變,下圖是無鎖的狀態下
無鎖、偏向鎖、輕量鎖、重量鎖、GC標記( 001,101,00,10,11)
關於對象狀態一共分為五種狀態,分別是無鎖、偏向鎖、輕量鎖、重量鎖、GC標記
鎖狀態 | 鎖標識 | 備注 |
---|---|---|
無鎖 | 001 | 對象頭中使用baised_lock + lock 一共3bit來表示無鎖和偏向鎖的 |
偏向鎖 | 101 | 對象頭中使用baised_lock + lock 一共3bit來表示無鎖和偏向鎖的 |
輕量鎖 | 00 | 只用到了lock標識位 |
重量鎖 | 10 | 只用到了lock標識位 |
GC標志 | 11 | 只用到了lock標識位 |
新建一個JOLExample3.java,代碼如下:
import org.openjdk.jol.info.ClassLayout;
import static java.lang.System.out;
public class JOLExample3 {
static A a;
public static void main(String[] args) throws InterruptedException {
a = new A();
out.println("before lock");
out.println(ClassLayout.parseInstance(a).toPrintable());
sync();
out.println("after lock");
out.println(ClassLayout.parseInstance(a).toPrintable());
}
private static void sync() {
synchronized (a) {
out.println("我不知道要打印什么");
}
}
}
}
查看運行結果如下:
上面這個程序只有一個線程去調用sync方法,應該是偏向鎖,但是你會發現輸出的結果(第一個字節)依然是00000001和無鎖的時候一模一樣,其實這是因為虛擬機在啟動的時候對於偏向鎖有延遲,如果沒有偏向鎖的延遲的話,虛擬機在啟動的時候,可能JVM某個線程調用你的線程,這樣就有可能變成了輕量鎖或者重量鎖,所以要做偏向鎖的延遲,那我們怎么看到打印的對象頭是偏向鎖呢?有兩種方式:第一種是加鎖之前先讓線程睡幾秒。第二種加上JVM的運行參數,關閉偏向鎖的延遲,具體的命令如下:
-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0
第一種方式:修改JOLExample3.java如下
import org.openjdk.jol.info.ClassLayout;
import static java.lang.System.out;
public class JOLExample3 {
static A a;
public static void main(String[] args) throws InterruptedException {
//切記延遲一定要放在對象創建之前,不然是無效的,因為在你對象創建之前,偏向鎖的延遲的時間
//沒有給你睡過去,這時候,對象已經創建了,對象頭的信息已經生成了。
Thread.sleep(5000);
a = new A();
out.println("before lock");
out.println(ClassLayout.parseInstance(a).toPrintable());
sync();
out.println("after lock");
out.println(ClassLayout.parseInstance(a).toPrintable());
}
private static void sync() {
synchronized (a) {
out.println("lock ing");
out.println(ClassLayout.parseInstance(a).toPrintable());
}
}
}
再次運行,查看結果如下:
可以發現已經變成了00000101,偏向鎖,需要注意的after lock,退出同步后依然保持了偏向信息。
第二種方式:利用jvm參數,首先我們先關閉睡眠5秒的,然后運行配置如下:
-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0
再次運行查看結果如下:
這時候大家會有疑問了,為什么在沒有加鎖之前是偏向鎖,准確的說,應該是叫可偏向的狀態,因為它后面沒有存線程的ID,當lock ing的時候,后面存儲的就是線程的ID(44969989)既然這兒存儲是線程的ID,那么HashCode又存儲到什么地方去了?是不是計算了HashCode就是不能偏向了?我們來驗證一下,計算完HashCode,還是不是偏向鎖了
我們再次修改JOLExample3.java,具體代碼如下:
import org.openjdk.jol.info.ClassLayout;
import static java.lang.System.out;
public class JOLExample3 {
static A a;
public static void main(String[] args) throws InterruptedException {
//切記延遲一定要放在對象創建之前,不然是無效的,因為在你對象創建之前,偏向鎖的延遲的時間
//沒有給你睡過去,這時候,對象已經創建了,對象頭的信息已經生成了。
//Thread.sleep(5000);
a = new A();
out.println("before lock");
out.println(ClassLayout.parseInstance(a).toPrintable());
a.hashCode();
sync();
out.println("after lock");
out.println(ClassLayout.parseInstance(a).toPrintable());
}
private static void sync() {
synchronized (a) {
out.println("lock ing");
out.println(ClassLayout.parseInstance(a).toPrintable());
}
}
}
同時關閉JVM中偏向鎖的延遲,運行的結果如下:
我們可以發現:在before lock的時候是可偏向的狀態,lock ing的時候變成了輕量鎖,after lock 的時候變成了無鎖,所以我們得出對象計算了HashCode,就不是偏向鎖了。
看完了偏向鎖的對象頭,我們再來看看輕量鎖的對象頭,輕量級鎖嘗試在應用層面解決線程同步問題,而不觸發操作系統的互斥操作,輕量級鎖減少多線程進入互斥的幾率,不能代替互斥。
創建JOLExample4.java,代碼如下:
import org.openjdk.jol.info.ClassLayout;
import static java.lang.System.out;
public class JOLExample4 {
static A a;
public static void main(String[] args) {
a = new A();
out.println("before lock");
out.println(ClassLayout.parseInstance(a).toPrintable());
sync();
out.println("after lock");
out.println(ClassLayout.parseInstance(a).toPrintable());
}
private static void sync() {
synchronized (a) {
out.println("lock ing");
out.println(ClassLayout.parseInstance(a).toPrintable());
}
}
}
運行結果如下:
可以得出:before lock 的時候是 00000001 無鎖的狀態,lock ing 的時候是 01010000 輕量鎖的狀態,after lock 的時候是 00000001 無鎖的狀態。
看完了輕量鎖的對象頭,我們再來看看重量鎖的對象頭,我們先創建一個JOLExample5.java具體代碼如下:
import org.openjdk.jol.info.ClassLayout;
import static java.lang.System.out;
public class JOLExample5 {
static A a;
public static void main(String[] args) throws InterruptedException {
a = new A();
out.println("before lock");
out.println(ClassLayout.parseInstance(a).toPrintable());
Thread t1 = new Thread(()->{
synchronized (a) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
out.println("t1 release");
}
});
t1.start();
Thread.sleep(1000);
out.println("t1 lock ing");
out.println(ClassLayout.parseInstance(a).toPrintable());
sync();
out.println("after lock");
out.println(ClassLayout.parseInstance(a).toPrintable());
System.gc();
out.println("after gc()");
out.println(ClassLayout.parseInstance(a).toPrintable());
}
private static void sync() {
synchronized (a) {
out.println("main lock ing");
out.println(ClassLayout.parseIstance(a).toPrintable());
}
}}
運行結果如下:
在加鎖之前(before lock)是 00000001 無鎖,這時候t1來加鎖,因為只有他一個線程所以輕量鎖(t1 lock ing 00010000)由於t1在run方法中睡眠了5秒,這時候主線程也來嘗試加鎖,這個時候就是兩個線程競爭了,所以是重量鎖(main lock ing 00101010)
當結束的時候,還是重量鎖(afteer lock 00101010),當執行一次gc操作過后發現變成了無鎖但是年齡加了1(after gc() 00001001)
還有一點需要我們注意的就是:當調用wait方法會直接變成重量鎖,我們來驗證一下,創建JOLExample6.java,代碼如下:
import org.openjdk.jol.info.ClassLayout;
import static java.lang.System.out;
public class JOLExample6 {
static A a;
public static void main(String[] args) throws Exception {
a = new A();
out.println("before lock");
out.println(ClassLayout.parseInstance(a).toPrintable());
Thread t1 = new Thread(() -> {
try {
synchronized (a) {
out.println("before wait");
out.println(ClassLayout.parseInstance(a).toPrintable());
a.wait();
out.println("after wait");
out.println(ClassLayout.parseInstance(a).toPrintable());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t1.start();
Thread.sleep(5000);
synchronized (a) {
a.notifyAll();
}
}
}
運行結果如下:
既然synchronized關鍵字有這三種鎖,我們簡單的比較它們之間的性能(粗略的比較下),書寫以下的代碼
public class A {
int i;
public synchronized void parse() {
i++;
}
}
//關閉偏向鎖延遲‐XX:BiasedLockingStartupDelay=0
public class JOLExample7 {
public static void main(String[] args) throws Exception {
A a = new A();
long start = System.currentTimeMillis();
//調用同步方法1000000000L 來計算1000000000L的++,對比偏向鎖和輕量級鎖的性能
//如果不出意外,結果灰常明顯
for (int i = 0; i < 1000000000L; i++) {
a.parse();
}
long end = System.currentTimeMillis();
System.out.println(String.format("%sms", end - start));
}
}
先運行加上jvm參數關閉偏向鎖延遲,就是偏向鎖,然后運行的結果如下:
我們在開啟偏向鎖延遲就是輕量鎖,然后運行結果如下:
最后我們在看重量鎖,具體代碼如下:
public class A { int i; public synchronized void parse() { JOLExample8.countDownLatch.countDown(); i++; } } import java.util.concurrent.CountDownLatch; public class JOLExample8 { static CountDownLatch countDownLatch = new CountDownLatch(1000000000); public static void main(String[] args) throws Exception { final A a = new A(); long start = System.currentTimeMillis(); //調用同步方法1000000000L 來計算1000000000L的++,對比各種鎖的性能 //如果不出意外,結果灰常明顯 for (int i = 0; i < 2; i++) { new Thread(() -> { while (countDownLatch.getCount() > 0) { a.parse(); } }).start(); } countDownLatch.await(); long end = System.currentTimeMillis(); System.out.println(String.format("%sms", end - start)); } }
重量級鎖的執行結果如下:
最后總結的結果如下:
偏向鎖 | 輕量鎖 | 重量鎖 |
---|---|---|
2355ms | 23564ms | 31227ms |
偏向鎖 輕量鎖 重量鎖 2355ms 23564ms 31227ms 最后我們再畫個圖總結下各種鎖的對象頭(只畫出了最重要的部分,其他的省略)
不同情況下的鎖升級策略
證明偏向鎖
證明偏向鎖 證明偏向鎖之前,咱們按下圖操作,給jvm添加查看全局配置的參數:
直接運行main方法,運行結果如下所示(由於篇幅問題,只截圖了關鍵部分)
由圖中的-XX:BiasedLockingStartupDelay=4000配置可知,jvm會在啟動虛擬機之后的4s后才會開啟偏向鎖功能。知道這個概念后,咱們再來科普下什么是偏向鎖。 所謂偏向鎖:即當一把鎖處於可偏向狀態時,當有線程持有這把鎖后,這把鎖將偏向於這個線程。這里提到了可偏向狀態,何為可偏向狀態呢?可偏向狀態是指在jvm開啟可偏向功能后,new出來的一個對象它都是可偏向狀態,即它的標識位為101,但是沒有具體的偏向某一個線程。 證明可偏向狀態和偏向鎖: 添加如下代碼並執行:
public class Valid { public static void main(String[] args) throws InterruptedException { // 這里要注意, 一定要在創建對象之前睡眠,若我們先創建對象,可以想一想會發生什么情況! // 那肯定是不會啟動偏向鎖的功能呀,我們都知道加鎖其實是給對象加了個標識 // 如果我們在偏向鎖功能未開啟之前創建了對象,很抱歉, // jvm沒有那么智能,后面不會去把這個對象改成可偏向狀態(是偏向鎖,但是沒有偏向具體 // 的線程) Thread.sleep(4100); System.out.println(ByteOrder.nativeOrder().toString()); User user = new User(); System.out.println("before lock"); System.out.println(ClassLayout.parseInstance(user).toPrintable()); synchronized (user) { System.out.println("lock ing"); System.out.println(ClassLayout.parseInstance(user).toPrintable()); } System.out.println("after lock"); System.out.println(ClassLayout.parseInstance(user).toPrintable()); } }
查看運行結果
證明一個對象調用了hashcode方法后無法再被標識為偏向鎖,而是升級成輕量鎖
編寫如下代碼(相對於上述代碼,僅在加鎖前調用了對象的hashcode方法):
public class Valid { public static void main(String[] args) throws InterruptedException { // 這里要注意, 一定要在創建對象之前睡眠,若我們先創建對象,可以想一想會發生什么情況! // 那肯定是不會啟動偏向鎖的功能呀,我們都知道加鎖其實是給對象加了個標識 // 如果我們在偏向鎖功能未開啟之前創建了對象,很抱歉, // jvm沒有那么智能,后面不會去把這個對象改成可偏向狀態(是偏向鎖,但是沒有偏向具體 // 的線程) Thread.sleep(4100); System.out.println(ByteOrder.nativeOrder().toString()); User user = new User(); System.out.println("before lock"); System.out.println(ClassLayout.parseInstance(user).toPrintable()); System.out.println(user.hashCode()); synchronized (user) { System.out.println("lock ing"); System.out.println(ClassLayout.parseInstance(user).toPrintable()); } System.out.println("after lock"); System.out.println(ClassLayout.parseInstance(user).toPrintable()); } }
證明輕量鎖
-
這里說下輕量鎖的概念:若線程是交替執行的,即上一個線程執行完釋放鎖后下一個線程再獲取鎖。若在jvm未開啟偏向鎖的過程中,對對象進行加鎖時,對象直接是輕量鎖。
public class Valid { public static void main(String[] args) throws InterruptedException { System.out.println(ByteOrder.nativeOrder().toString()); User user = new User(); System.out.println("before lock"); System.out.println(ClassLayout.parseInstance(user).toPrintable()); synchronized (user) { System.out.println("lock ing"); System.out.println(ClassLayout.parseInstance(user).toPrintable()); } System.out.println("after lock"); System.out.println(ClassLayout.parseInstance(user).toPrintable()); } }
證明偏向鎖膨脹為輕量鎖
public class Valid { public static void main(String[] args) throws InterruptedException { // 開啟偏向鎖功能 Thread.sleep(4100); System.out.println(ByteOrder.nativeOrder().toString()); User user = new User(); System.out.println("before lock" + ClassLayout.parseInstance(user).toPrintable()); synchronized (user) { System.out.println("lock ing" + ClassLayout.parseInstance(user).toPrintable()); } System.out.println("after lock" + ClassLayout.parseInstance(user).toPrintable()); // 開啟線程來獲取鎖 Thread t1 = new Thread(() -> { synchronized (user) { System.out.println("other t1 thread get lock" + ClassLayout.parseInstance(user).toPrintable()); } }, "t1"); t1.start(); // 等待t1執行完后再打印一次鎖信息 t1.join(); System.out.println("after t1 thread release lock" + ClassLayout.parseInstance(user).toPrintable()); } }
證明重量鎖
-
重量鎖概念:多個線程存在激烈的競爭時,鎖會膨脹成重量鎖,且不可逆!
-
典型案例:生產者消費者模型:
public class ValidSynchronized { static Object lock = new Object(); static volatile LinkedList<String> queue = new LinkedList<>(); public static void main(String[] args) throws InterruptedException { System.out.println("before lock"); System.out.println(ClassLayout.parseInstance(lock).toPrintable()); Consumer consumer = new Consumer(); Producer producer = new Producer(); consumer.start(); producer.start(); Thread.sleep(500); consumer.interrupt(); producer.interrupt(); // 睡眠3s ==> 目的是為了讓鎖自己釋放,防止在釋放過程中打印鎖的狀態出現重量鎖的情況 Thread.sleep(3000); System.out.println("after lock"); System.out.println(ClassLayout.parseInstance(lock).toPrintable()); } } class Producer extends Thread { @Override public void run() { while (!isInterrupted()) { synchronized (ValidSynchronized.lock) { System.out.println("lock ing"); System.out.println(ClassLayout.parseInstance(ValidSynchronized.lock).toPrintable()); String message = UUID.randomUUID().toString(); System.out.println("生產者生產消息:" + message); ValidSynchronized.queue.offer(message); try { // 生產者自己wait,目的是釋放鎖 ValidSynchronized.lock.notify(); ValidSynchronized.lock.wait(); TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { this.interrupt(); } } } } } class Consumer extends Thread { @Override public void run() { while (!isInterrupted()) { synchronized (ValidSynchronized.lock) { if (ValidSynchronized.queue.size() == 0) { try { ValidSynchronized.lock.wait(); ValidSynchronized.lock.notify(); } catch (InterruptedException e) { e.printStackTrace(); } } String message = ValidSynchronized.queue.pollLast(); System.out.println("消費者消費消息:" + message); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { this.interrupt(); } } } } }
證明調用wait方法后,鎖會升級為重量鎖
public class ValidWait { public static void main(String[] args) throws InterruptedException { Thread.sleep(4100); final User user = new User(); System.out.println("before lock"); System.out.println(ClassLayout.parseInstance(user).toPrintable()); Thread t1 = new Thread(() -> { synchronized (user) { System.out.println("lock ing"); System.out.println("before wait"); System.out.println(ClassLayout.parseInstance(user).toPrintable()); try { user.wait(); System.out.println("after wait"); System.out.println(ClassLayout.parseInstance(user).toPrintable()); } catch (InterruptedException e) { e.printStackTrace(); } } }, "t1"); t1.start(); // 主線程睡眠3s后,喚醒t1線程 Thread.sleep(3000); System.out.println("主線程查看鎖,變成了重量鎖"); System.out.println(ClassLayout.parseInstance(user).toPrintable()); } }
總結
偏向鎖和hashcode是互斥的,只能存在一個。 jvm默認對偏向鎖功能是延遲加載的,大概時間為4s鍾,可以添加JVM參數: -XX:BiasedLockingStartupDelay=0來設置延遲時間為0。偏向鎖的延遲加載關閉后,基本上所有的鎖都會為可偏向狀態,即mark word為101,但是它還沒有具體偏向的線程信息 偏向鎖退出同步塊后依然也是偏向鎖 重量級鎖之所以重量就是因為狀態不停的切換,最終映射到代碼層面就是不停的調用操作系統函數(最終會調用到jvm的mutex類) 調用鎖對象的wait方法時,當前鎖對象會立馬升級為重量級鎖 偏向鎖只要被其他線程拿到了,此時偏向鎖會膨脹。膨脹為輕量鎖