Java中的synchronized鎖膨脹詳解
1. 基本概念
Java對(duì)象頭
Java對(duì)象的對(duì)象頭信息中的 Mark Word 主要是用來記錄對(duì)象的鎖信息的。
現(xiàn)在看一下 Mark Word 的對(duì)象頭信息。如下:

其實(shí)可以根據(jù) mark word 的后3位就可以判斷出當(dāng)前的鎖是屬于哪一種鎖。注意:表格中的正常鎖其實(shí)就是無鎖狀態(tài)了。
2. 幾種鎖以及原理
無鎖
正常創(chuàng)建的對(duì)象,狀態(tài)為無鎖。對(duì)象頭的Mark Word 中主要記錄了 對(duì)象的年齡,也就是經(jīng)歷了多少次GC還存活下來。
偏向鎖
對(duì)象頭的Mark Word 中記錄的信息比正常鎖多的是 記錄了線程的信息,也就是線程的id。偏向鎖,字面意思就是比較偏心,偏向于某個(gè)線程。
偏向鎖加鎖原理
經(jīng)過分析可以看到,thread 字段剛開始的時(shí)候?yàn)?。工作原理是 cas(thread,0,當(dāng)前線程), 也就是判斷當(dāng)前的 thread的值如果是0的話,就賦值為當(dāng)前線程。如果cas 拿鎖失敗了,那么有倆種情況。
重入
當(dāng)前線程已經(jīng)是偏向鎖的持有者了,那么只需要判斷 thread 字段是否就是當(dāng)前線程,如果是的話,其實(shí)就是當(dāng)前線程已經(jīng)拿到這把鎖了。那么此時(shí)就會(huì)在棧中創(chuàng)建lockRecord。因?yàn)闂J蔷€程私有的。lockRecord 其實(shí)包含倆部分的內(nèi)容,第一部分內(nèi)容和鎖對(duì)象的 markword中的對(duì)象是完全一樣的。記為M區(qū)。第二部分是記錄了當(dāng)前對(duì)象,也就是加鎖對(duì)象的指針。記為O區(qū)。在第一次加鎖的時(shí)候,是不需要?jiǎng)?chuàng)建 lockRecord的。只有在重入的時(shí)候,才需要?jiǎng)?chuàng)建 lockRecord的。
其它線程持有鎖
那么此時(shí)就會(huì)升級(jí)為輕量級(jí)鎖。
偏向鎖解鎖原理
解鎖過程非常簡(jiǎn)單,只需要在當(dāng)前線程的棧上刪除最近的lockRecord對(duì)象就可以了。因?yàn)橹厝氲臅r(shí)候是不斷的創(chuàng)建這些lockRecord對(duì)象的。
輕量級(jí)鎖
主要是將鎖對(duì)象的Mark Word更新為指向Lock Record的指針,也就是鎖記錄。注意,這個(gè)鎖記錄是在棧上的。
輕量級(jí)鎖加鎖原理
線程在自己的棧楨中創(chuàng)建鎖記錄 LockRecord。

將鎖對(duì)象的對(duì)象頭中的MarkWord復(fù)制到線程的剛剛創(chuàng)建的鎖記錄中。
將鎖對(duì)象的對(duì)象頭的MarkWord替換為指向鎖記錄的指針。也就是進(jìn)行cas操作。cas(ptr, null, lockRecord)。也就是如果對(duì)象頭的ptr指針如果為空,那么就賦值為當(dāng)前的lockRecord對(duì)象。并將線程棧幀中的Lock Record里的owner指針指向Object的 Mark Word。如果賦值成功了,那么就可以認(rèn)為加鎖成功了。

如果加鎖失敗了,也有倆種情況。
重入。
就是ptr 指向的是另外的一個(gè)lockRecord對(duì)象,但是也是當(dāng)前線程創(chuàng)建的。也就是在當(dāng)前線程的棧上是可以找到這個(gè)lockRecord對(duì)象。首先在棧上檢查是否可以找到這個(gè)鎖對(duì)象。如果可以找到,就是重入。如果是重入,那么就需要將鎖記錄中的Owner指針指向鎖對(duì)象。
其它線程持有鎖
那么就進(jìn)入重量級(jí)鎖。
輕量級(jí)鎖解鎖原理
釋放鎖的時(shí)候,從棧上進(jìn)行掃描,從后往前 找到最近的lockRecord,刪除。
重量級(jí)鎖
主要是指向一個(gè)monitor 對(duì)象。
分析如下: synchronized重量級(jí)鎖解析
3. 看一段程序,看一下鎖是如何進(jìn)行演變的。
package org.example;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
/**
* @author frank wy170862@alibaba-inc.com
* @date 2020-02-24
*/
public class A {
public static void main(String[] args) throws Exception {
test2();
}
/**
* 一、正常創(chuàng)建的對(duì)象,狀態(tài)為無鎖,觀察hashcode和age的變化。hashcode 的計(jì)算是惰性的。剛開始的時(shí)候,對(duì)象的hashcode是0。只有在計(jì)算了hashcode以后,才會(huì)將hashcode存儲(chǔ)到對(duì)象頭,下次取hashcode的時(shí)候,就可以直接從對(duì)象頭中取出hashcode了。
*
* 鎖狀態(tài):無鎖,hashCode:0,age: 0
* ---------------
*
* 運(yùn)行hashcode方法,得到hashcode:648129364
* 鎖狀態(tài):無鎖,hashCode:648129364,age: 0
* ---------------
*
* [GC (System.gc()) [PSYoungGen: 6568K->1095K(76288K)] 6568K->1103K(251392K), 0.0017301 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
* [Full GC (System.gc()) [PSYoungGen: 1095K->0K(76288K)] [ParOldGen: 8K->932K(175104K)] 1103K->932K(251392K), [Metaspace: 3084K->3084K(1056768K)], 0.0054386 secs] [Times: user=0.02 sys=0.01, real=0.01 secs]
* 運(yùn)行一次gc,obj的age+1
* 鎖狀態(tài):無鎖,hashCode:648129364,age: 1
* ---------------
* @throws Exception
*/
private static void test1() throws Exception {
Object a = new Object();
printLockHeader(a);
System.out.println("運(yùn)行hashcode方法,得到hashcode:" + a.hashCode());;
printLockHeader(a);
System.gc();
System.out.println("運(yùn)行一次gc,obj的age+1");
// sleep 1s 讓gc完成,但是不一定能100%觸發(fā)gc,可以配合添加運(yùn)行參數(shù) -XX:+PrintGCDetails,觀察確實(shí)gc了
Thread.sleep(1000);
printLockHeader(a);
}
/**
* 二、正常創(chuàng)建的對(duì)象,狀態(tài)為無鎖,無鎖狀態(tài)直接加鎖會(huì)變成輕量鎖
*
* 鎖狀態(tài):無鎖,hashCode:0,age: 0
* ---------------
*
* 對(duì)a加鎖后
* 鎖狀態(tài):輕量級(jí)鎖,LockRecord地址:1c00010c6e28
* ---------------
* @throws Exception
*/
private static void test2() throws Exception {
Object a = new Object();
printLockHeader(a);
synchronized (a){
System.out.println("對(duì)a加鎖后");
printLockHeader(a);
}
}
/**
* 三、程序啟動(dòng)一定時(shí)間后,正常創(chuàng)建的對(duì)象,狀態(tài)為偏向鎖且thread為0,此時(shí)加鎖默認(rèn)為偏向鎖
* 一段時(shí)間一般是幾秒,-XX:BiasedLockingStartupDelay=0可以指定默認(rèn)就使用偏向鎖,而不是無鎖
*
* 鎖狀態(tài):偏向鎖,thread:0,epoch: 0,age: 0
* ---------------
*
* 對(duì)a加鎖后
* 鎖狀態(tài):偏向鎖,thread:137069895700,epoch: 0,age: 0
* ---------------
*
* 偏向鎖重入后
* 鎖狀態(tài):偏向鎖,thread:137069895700,epoch: 0,age: 0
* ---------------
* @throws Exception
*/
private static void test3() throws Exception {
Thread.sleep(5*1000);
Object a = new Object();
printLockHeader(a);
synchronized (a){
System.out.println("對(duì)a加鎖后");
printLockHeader(a);
System.out.println("偏向鎖重入后");
synchronized (a){
printLockHeader(a);
}
}
}
/**
* 四、基于三,當(dāng)另一個(gè)線程嘗試使用對(duì)象鎖的時(shí)候,升級(jí)為輕量鎖
*
* 鎖狀態(tài):偏向鎖,thread:0,epoch: 0,age: 0
* ---------------
*
* 線程1對(duì)a加鎖后
* 鎖狀態(tài):偏向鎖,thread:137122299998,epoch: 0,age: 0
* ---------------
*
* 鎖釋放了
* 線程2對(duì)a加鎖后
* 鎖狀態(tài):輕量級(jí)鎖,LockRecord地址:1c00015e6a18
* ---------------
* @throws Exception
*/
private static void test4() throws Exception {
Thread.sleep(5*1000);
Object a = new Object();
printLockHeader(a);
new Thread(
()->{
synchronized (a){
System.out.println("線程1對(duì)a加鎖后");
try {
printLockHeader(a);
} catch (Exception e) {
e.printStackTrace();
}
}
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
).start();
// 中間sleep1s,保證鎖釋放掉,使兩個(gè)線程不會(huì)有競(jìng)爭(zhēng)關(guān)系
Thread.sleep(1000);
System.out.println("鎖釋放了");
new Thread(
()->{
synchronized (a){
System.out.println("線程2對(duì)a加鎖后");
try {
printLockHeader(a);
} catch (Exception e) {
e.printStackTrace();
}
}
}
).start();
}
/**
* 五、基于四,當(dāng)產(chǎn)生競(jìng)爭(zhēng)的時(shí)候偏向鎖直接升級(jí)為重量級(jí)鎖
*
* 鎖狀態(tài):偏向鎖,thread:0,epoch: 0,age: 0
* ---------------
*
* 線程1對(duì)a加鎖后
* 鎖狀態(tài):偏向鎖,thread:137025283340,epoch: 0,age: 0
* ---------------
*
* 線程2對(duì)a加鎖后
* 鎖狀態(tài):重量級(jí)鎖,Monitor地址:1fe758800a02
* ---------------
* @throws Exception
*/
private static void test5() throws Exception {
Thread.sleep(5*1000);
Object a = new Object();
printLockHeader(a);
new Thread(
()->{
synchronized (a){
System.out.println("線程1對(duì)a加鎖后");
try {
printLockHeader(a);
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
).start();
new Thread(
()->{
synchronized (a){
System.out.println("線程2對(duì)a加鎖后");
try {
printLockHeader(a);
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
).start();
}
/**
* 六、四+五 演示偏向-輕量-重量過程
*
* 鎖狀態(tài):偏向鎖,thread:0,epoch: 0,age: 0
* ---------------
*
* 線程1對(duì)a加鎖后
* 鎖狀態(tài):偏向鎖,thread:137272648594,epoch: 0,age: 0
* ---------------
*
* 鎖釋放
* 線程2對(duì)a加鎖后
* 鎖狀態(tài):輕量級(jí)鎖,LockRecord地址:1c0001b43e18
* ---------------
*
* 鎖釋放
* 線程3對(duì)a加鎖后
* 鎖狀態(tài):輕量級(jí)鎖,LockRecord地址:1c0001b43e18
* ---------------
*
* 線程4對(duì)a加鎖后
* 鎖狀態(tài):重量級(jí)鎖,Monitor地址:1ff616600f02
* ---------------
* @throws Exception
*/
private static void test6() throws Exception {
Thread.sleep(5*1000);
Object a = new Object();
printLockHeader(a);
new Thread(
()->{
synchronized (a){
System.out.println("線程1對(duì)a加鎖后");
try {
printLockHeader(a);
} catch (Exception e) {
e.printStackTrace();
}
}
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
).start();
// 中間sleep1s,使線程不會(huì)有競(jìng)爭(zhēng)關(guān)系
Thread.sleep(1000);
System.out.println("鎖釋放");
new Thread(
()->{
synchronized (a){
System.out.println("線程2對(duì)a加鎖后");
try {
printLockHeader(a);
} catch (Exception e) {
e.printStackTrace();
}
}
}
).start();
// 中間sleep1s,使線程不會(huì)有競(jìng)爭(zhēng)關(guān)系
Thread.sleep(1000);
System.out.println("鎖釋放");
new Thread(
()->{
synchronized (a){
System.out.println("線程3對(duì)a加鎖后");
try {
printLockHeader(a);
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
).start();
// 此時(shí)不再sleep,使線程必然發(fā)生競(jìng)爭(zhēng),升級(jí)為重量級(jí)鎖
new Thread(
()->{
synchronized (a){
System.out.println("線程4對(duì)a加鎖后");
try {
printLockHeader(a);
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
).start();
}
private static Unsafe getUnsafe() throws Exception {
Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");
Field field = unsafeClass.getDeclaredField("theUnsafe");
field.setAccessible(true);
return (Unsafe) field.get(null);
}
private static void printLockHeader(Object obj) throws Exception {
Unsafe us = getUnsafe();
StringBuilder sb = new StringBuilder();
int status = us.getByte(obj, 0L) & 0B11;
// 0 輕量級(jí) 1 無鎖或偏向 2 重量級(jí) 3 GC標(biāo)記
switch (status){
case 0:
// ptr_to_lock_record:62|lock:2
long ptrToLockRecord =
(byteMod(us.getByte(obj, 0L))>>2) +
(byteMod(us.getByte(obj, 1L))<<6) +
(byteMod(us.getByte(obj, 2L))<<14) +
(byteMod(us.getByte(obj, 3L))<<22) +
(byteMod(us.getByte(obj, 4L))<<30) +
(byteMod(us.getByte(obj, 5L))<<38) +
(byteMod(us.getByte(obj, 6L))<<46) +
(byteMod(us.getByte(obj, 7L))<<54);
sb.append("鎖狀態(tài):輕量級(jí)鎖,LockRecord地址:")
.append(Long.toHexString(ptrToLockRecord))
;
break;
case 1:
boolean biased = (us.getByte(obj, 0L)&4) == 4;
if(!biased){
// unused:25 | identity_hashcode:31 | unused:1 | age:4 | biased_lock:1 | lock:2
int hashCode = (int)(byteMod(us.getByte(obj, 1L))
+ (byteMod(us.getByte(obj, 2L))<<8)
+ (byteMod(us.getByte(obj, 3L))<<16)
+ ((byteMod(us.getByte(obj, 4L))&Integer.MAX_VALUE) <<24))
;
int age = (us.getByte(obj,0L)>>3)&0B1111;
sb.append("鎖狀態(tài):無鎖,hashCode:")
.append(hashCode)
.append(",age: ")
.append(age);
}else{
//thread:54|epoch:2|unused:1| age:4 | biased_lock:1 | lock:2
long thread = (byteMod(us.getByte(obj, 1L))>>2) +
(byteMod(us.getByte(obj, 2L))<<6) +
(byteMod(us.getByte(obj, 3L))<<14) +
(byteMod(us.getByte(obj, 4L))<<22) +
(byteMod(us.getByte(obj, 5L))<<30) +
(byteMod(us.getByte(obj, 6L))<<38) +
(byteMod(us.getByte(obj, 7L))<<46);
;
int epoch = us.getByte(obj, 1L) & 0B11;
int age = (us.getByte(obj,0L)>>3)&0B1111;
sb.append("鎖狀態(tài):偏向鎖,thread:")
.append(thread)
.append(",epoch: ")
.append(epoch)
.append(",age: ")
.append(age);
}
break;
case 2:
// ptr_to_heavyweight_monitor:62| lock:2
long ptrToMonitor =
(byteMod(us.getByte(obj, 0L))>>2) +
(byteMod(us.getByte(obj, 1L))<<6) +
(byteMod(us.getByte(obj, 2L))<<14) +
(byteMod(us.getByte(obj, 3L))<<22) +
(byteMod(us.getByte(obj, 4L))<<30) +
(byteMod(us.getByte(obj, 5L))<<38) +
(byteMod(us.getByte(obj, 6L))<<46) +
(byteMod(us.getByte(obj, 7L))<<54);
sb.append("鎖狀態(tài):重量級(jí)鎖,Monitor地址:")
.append(Long.toHexString(ptrToMonitor))
;
break;
case 3:
sb.append("鎖狀態(tài):GC標(biāo)記");
break;
default:
break;
}
if(obj instanceof Object[]){
int arrLen = us.getInt(obj, 3L);
sb.append("對(duì)象為數(shù)組類型,數(shù)組長(zhǎng)度:")
.append(arrLen);
}
sb.append("\n").append("---------------").append("\n");
System.out.println(sb.toString());
}
private static long byteMod(byte b){
if(b>=0){
return b;
}
return b + 256;
}
}4. 總結(jié)
剛開始,程序開始運(yùn)行的時(shí)候,創(chuàng)建的對(duì)象都屬于無鎖對(duì)象。程序運(yùn)行一段時(shí)間后,一段時(shí)間一般指的是4秒鐘,創(chuàng)建的對(duì)象屬于偏向鎖對(duì)象。無鎖狀態(tài)下直接加鎖會(huì)變?yōu)檩p量級(jí)鎖。偏向鎖狀態(tài)下的對(duì)象,加鎖的話,會(huì)對(duì)當(dāng)前線程有一個(gè)偏向。如果此時(shí)再有另外的一個(gè)線程過來申請(qǐng)鎖,那么就會(huì)升級(jí)為輕量級(jí)鎖對(duì)象。輕量級(jí)鎖下,如果存在競(jìng)爭(zhēng),那么一定會(huì)升級(jí)為重量級(jí)鎖。當(dāng)然,偏向鎖狀態(tài)下,如果存在競(jìng)爭(zhēng),也會(huì)升級(jí)為重量級(jí)鎖。
總結(jié)起來就是:如果存在競(jìng)爭(zhēng),那么一定會(huì)升級(jí)為重量級(jí)鎖。如果存在另外一個(gè)線程想要拿到這把鎖,就會(huì)升級(jí)為輕量級(jí)鎖。偏向鎖和輕量級(jí)鎖的區(qū)別是:輕量級(jí)鎖是可以另外一個(gè)線程來拿鎖,也就是一個(gè)線程釋放掉鎖以后,另外一個(gè)線程可以來拿鎖,這就是輕量級(jí)鎖。如果一個(gè)線程釋放掉鎖以后,另外一個(gè)線程拿不到這把鎖,那么就屬于偏向鎖。
到此這篇關(guān)于Java中的synchronized鎖膨脹詳解的文章就介紹到這了,更多相關(guān)synchronized鎖膨脹內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring中過濾器(Filter)和攔截器(Interceptor)的區(qū)別和聯(lián)系解析
在我們?nèi)粘5拈_發(fā)中,我們經(jīng)常會(huì)用到Filter和Interceptor,這篇文章主要介紹了Spring中過濾器(Filter)和攔截器(Interceptor)的區(qū)別和聯(lián)系?,需要的朋友可以參考下2022-10-10
Unity2019-2020 個(gè)人版官方免費(fèi)激活詳細(xì)方法
這篇文章主要介紹了Unity2019-2020 個(gè)人版官方免費(fèi)激活詳細(xì)方法,激活方法分位兩種一種是激活新許可證,一種是手動(dòng)激活,感興趣的朋友跟隨小編一起看看吧2021-04-04
SVN出現(xiàn)提示org.apache.subversion.javahl.ClientException: Attempt
這篇文章主要介紹了SVN出現(xiàn)提示org.apache.subversion.javahl.ClientException: Attempted to lock an already-locked dir解決方案的相關(guān)資料,需要的朋友可以參考下2016-12-12
基于Java開發(fā)一個(gè)極簡(jiǎn)版敏感詞檢測(cè)工具
這篇文章主要為大家詳細(xì)介紹了如何基于Java開發(fā)一個(gè)極簡(jiǎn)版敏感詞檢測(cè)工具,文中的示例代碼簡(jiǎn)潔易懂,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2025-08-08

