Synchronized鎖升級(jí)過(guò)程

new出來(lái)對(duì)象在堆內(nèi)存中的內(nèi)存布局

  1. markword 8個(gè)字節(jié) (synchronized主要影響的是markword)markword記錄了鎖信息,gc信息,hashcode
  2. klass pointer 指向類.class 即表示對(duì)象屬于哪個(gè)class的對(duì)象 默認(rèn)4個(gè)字節(jié)
  3. 成員變量,比如int m=8;int類型的變量占據(jù)4個(gè)字節(jié)
  4. padding對(duì)齊

64位的虛擬機(jī),對(duì)齊是8個(gè)字節(jié),之所以是8個(gè)字節(jié)是指整體這個(gè)對(duì)象所占用字節(jié)總和必須能被8整除

JOL=Java Object Layout工具的使用

jol是對(duì)象內(nèi)存布局查看工具

引入

        <dependency>
            <groupId>org.openjdk.jol</groupId>
            <artifactId>jol-core</artifactId>
            <version>0.9</version>
        </dependency>

案例

情況一:下面我們用jol解析new出來(lái)的對(duì)象o的內(nèi)存布局

public class JolTest {
    public static void main(String[] args) {
        Object o=new Object();
        System.out.println(ClassLayout.parseInstance(o).toPrintable());
    }
}

執(zhí)行結(jié)果:

從第0個(gè)位置往后數(shù)4個(gè)字節(jié)和從第4個(gè)位置往后數(shù)4個(gè)字節(jié)總共8個(gè)字節(jié)這兩部分加起來(lái)8個(gè)字節(jié)是markword,從第8個(gè)字節(jié)開始往后數(shù)4個(gè)是klass pointer,從第12個(gè)字節(jié)開始加4個(gè)字節(jié)對(duì)齊組成16個(gè)字節(jié)

情況二:下面我們對(duì)對(duì)象o加一把鎖,然后來(lái)再看它的內(nèi)存布局變化

public class JolTest {
    public static void main(String[] args) {
        Object o=new Object();
        System.out.println(ClassLayout.parseInstance(o).toPrintable());

        synchronized (o){
            System.out.println(ClassLayout.parseInstance(o).toPrintable());
        }
    }
}

執(zhí)行結(jié)果:

可以看到鎖定對(duì)象前對(duì)象的markword和鎖定對(duì)象后對(duì)象的markword不一樣了,因此我們可以認(rèn)識(shí)到,鎖定對(duì)象后對(duì)象的markword中一定有鎖信息

每個(gè)組的第一行的括號(hào)后面的第一個(gè)8位的最后3位可以看鎖信息,不如上面是001(無(wú)鎖態(tài)),下面是000(自旋鎖)

左邊是右邊二進(jìn)制位的十六進(jìn)制表示形式,比如C8的二進(jìn)制表示為11001000

總結(jié)

markword記錄了鎖信息,gc信息,hashcode

Synchronized鎖升級(jí)過(guò)程

Synchronized鎖在jdk1.2之前效率非常低,后面逐步優(yōu)化,在jdk1.6以后優(yōu)化到了一個(gè)比較好的狀態(tài),這個(gè)狀態(tài)就是Synchronized鎖升級(jí)過(guò)程

用戶態(tài)和內(nèi)核態(tài)

windows或者linux操作系統(tǒng)都有一個(gè)內(nèi)核,稱為kernel。最早的時(shí)候,kernel和我們的應(yīng)用程序不區(qū)分,這就導(dǎo)致應(yīng)用程序直接訪問(wèn)硬件(內(nèi)存,網(wǎng)卡,顯示器),很容易把硬件搞壞,比如直接把操作系統(tǒng)用的內(nèi)存干掉,導(dǎo)致操作系統(tǒng)down

鑒于此,現(xiàn)在的操作系統(tǒng)一般分為兩層,kernel工作的是內(nèi)核態(tài),我們自己的應(yīng)用程序工作的時(shí)候是用戶態(tài)。這樣,如果用戶態(tài)的程序要訪問(wèn)內(nèi)存必須先經(jīng)過(guò)內(nèi)核態(tài)的允許,從用戶態(tài)轉(zhuǎn)到內(nèi)核態(tài),拿到結(jié)果以后再將數(shù)據(jù)從內(nèi)核態(tài)轉(zhuǎn)移到用戶態(tài)

JVM相對(duì)于操作系統(tǒng)來(lái)說(shuō)也就是一個(gè)普通程序。

JDK早期,Synchronized叫做重量級(jí)鎖,因?yàn)樯暾?qǐng)鎖必須通過(guò)內(nèi)核kernel,系統(tǒng)調(diào)用才能拿到這把鎖。之所以說(shuō)是重就是因?yàn)樾枰?jīng)過(guò)操作系統(tǒng)內(nèi)核的幫助,因此這是一個(gè)效率很低的耗時(shí)操作,即使線程很少的情況下

后面對(duì)鎖逐步優(yōu)化,就是某些情況下不再需要經(jīng)過(guò)內(nèi)核,直接在用戶空間就可以解決,比如CAS,是輕量級(jí)鎖

Synchronized鎖升級(jí)過(guò)程

如果最后2位是0,1,則偏向鎖和無(wú)鎖態(tài)相同因此無(wú)法區(qū)分,所以再加一位,因此第三位叫偏向鎖位

無(wú)鎖態(tài)-> 偏向鎖 ->輕量級(jí)鎖(又稱自旋鎖/無(wú)鎖(之所以叫原因是沒(méi)有內(nèi)核狀態(tài)的鎖即CAS))

偏向鎖和自旋鎖都是在用戶空間完成的
重量級(jí)鎖是需要向內(nèi)核申請(qǐng)

偏向鎖和偏向鎖的升級(jí)

偏向鎖默認(rèn)是啟動(dòng)的,但是會(huì)延遲,JVM啟動(dòng)以后,4秒以后偏向鎖才會(huì)起作用

就是偏向你一個(gè)人,偏向鎖實(shí)際上就是沒(méi)有鎖,將自己的id貼上去即可,之所以會(huì)有偏向鎖,因?yàn)楸緛?lái)很多時(shí)間就是只有一個(gè)線程在運(yùn)行,但這個(gè)方法天生就加了一個(gè)synchronized,如果向內(nèi)核申請(qǐng)一個(gè)重量級(jí)鎖效率太低了。
比如我們業(yè)務(wù)白天訪問(wèn)量很大,存在并發(fā)訪問(wèn)情況,可能晚上就一個(gè)人訪問(wèn)或者沒(méi)有,如果我們直接加個(gè)重量級(jí)鎖很明顯效率很低。

因此我們直接只是單純加一個(gè)標(biāo)記,比如張三要上廁所,他在門上貼一張條張三,然后就進(jìn)去。如果這時(shí)候有其他兩個(gè)線程也來(lái)?yè)屵@個(gè)資源的話,那么就要出現(xiàn)鎖競(jìng)爭(zhēng),讓張三暫停,然后將張三加的偏向鎖的標(biāo)記撤下來(lái),即鎖撤銷(將張三的紙條撕下來(lái)),開始上自旋鎖。因?yàn)槠蜴i并未加鎖,只是貼了個(gè)標(biāo)記,不存在鎖釋放概念:張三醒過(guò)來(lái),繼續(xù)開始執(zhí)行。其他線程等張三執(zhí)行完后(之所以要等是因?yàn)槿绻渌€程搶占到了鎖,那么張三這次操作就不具備原子性,是線程不安全的)釋放鎖后,誰(shuí)能將自己的lock Record貼到門上去,誰(shuí)就搶到了這把鎖。當(dāng)競(jìng)爭(zhēng)越來(lái)越激烈的時(shí)候,才會(huì)升級(jí)為重量級(jí)鎖

分析jvm源碼(biasedLocking.cpp)解析的偏向鎖升級(jí)流程,示例中:線程1當(dāng)前擁有偏向鎖對(duì)象,線程2是需要競(jìng)爭(zhēng)到偏向鎖。

線程2來(lái)競(jìng)爭(zhēng)鎖對(duì)象:
判斷當(dāng)前對(duì)象頭是否是偏向鎖;
判斷擁有偏向鎖的線程1是否還存在;
線程1不存在,直接設(shè)置偏向鎖標(biāo)識(shí)為0(線程1執(zhí)行完畢后,不會(huì)主動(dòng)去釋放偏向鎖);
使用cas替換偏向鎖線程ID為線程2,鎖不升級(jí),仍為偏向鎖;
線程1仍然存在,暫停線程1;
設(shè)置鎖標(biāo)志位為00(變?yōu)檩p量級(jí)鎖),偏向鎖為0;
從線程1的空閑monitor record中讀取一條,放至線程1的當(dāng)前monitor record中;
更新mark word,將mark word指向線程1中monitor record的指針;
繼續(xù)執(zhí)行線程1的代碼;
鎖升級(jí)為輕量級(jí)鎖;
線程2自旋來(lái)獲取鎖對(duì)象;

偏向鎖升級(jí)輕量級(jí)鎖條件:只要再來(lái)一個(gè)線程競(jìng)爭(zhēng)就升級(jí)
偏向鎖和自旋鎖的區(qū)別:自旋鎖是需要耗費(fèi)cpu資源的

有了輕量級(jí)鎖,為什么還要升級(jí)重量級(jí)鎖

輕量級(jí)鎖適合鎖定以后執(zhí)行時(shí)間較短的場(chǎng)景,或者線程數(shù)較少的場(chǎng)景。和重量級(jí)鎖最重要的區(qū)別就是自旋鎖是需要占用cpu資源的,因?yàn)樽孕鸵h(huán),而重量級(jí)鎖是不需要消耗cpu資源的,原因是重量級(jí)鎖下面有兩個(gè)隊(duì)列,一個(gè)競(jìng)爭(zhēng)隊(duì)列,一個(gè)等待隊(duì)列。比如之前可能有100個(gè)線程在自旋消耗cpu資源,現(xiàn)在不需要再自旋,將其扔到一個(gè)等待隊(duì)列waitSet中等著即可,什么時(shí)候輪到這個(gè)線程,操作系統(tǒng)將其叫醒然后執(zhí)行,輪不到那個(gè)線程的時(shí)候,他就等著即可,屬于阻塞

輕量級(jí)鎖升級(jí)重量級(jí)鎖條件:自旋超過(guò)10次,升級(jí)為重量級(jí)鎖,原因:如果太多線程自旋CPU消耗過(guò)大,不然升級(jí)為重量級(jí)鎖,進(jìn)入等待隊(duì)列(不消耗cpu)

總結(jié)

new一個(gè)對(duì)象,加鎖的時(shí)候,先加上偏向鎖,有輕度競(jìng)爭(zhēng)的時(shí)候升級(jí)為輕量級(jí)鎖,輕量級(jí)鎖有重度競(jìng)爭(zhēng)的話升級(jí)重量級(jí)鎖(鎖膨脹)。
如果有耗時(shí)過(guò)長(zhǎng)的操作或者wait'操作,偏向鎖會(huì)直接升級(jí)重量級(jí)鎖

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容