自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Java中synchronized的底層實現(xiàn)原理

開發(fā) 前端
通過synchronized進行加鎖,就是通過對象頭的Mark Word關(guān)聯(lián)起來的,里面記錄著鎖狀態(tài)和占有鎖的線程地址指針。

一、對象頭、Mark Word、monitor、synchronized怎么關(guān)聯(lián)起來

(1)首先java里面每個對象JVM底層都會為它創(chuàng)建一個監(jiān)視器monitor,這個是JVM層次為我們保證的。這個監(jiān)視器就類似一個鎖,哪個線程持有這個monitor的操作權(quán),就相當于獲取到了鎖

(2)其次synchronized 修飾的代碼或者方法,底層會生成兩條指令分別為monitorenter、monitorexit。

(3)進入synchronized的代碼塊之前會執(zhí)行monitorenter指令,去申請monitor監(jiān)視器的操作權(quán),如果申請成功了,就相當于獲取到了鎖。如果已經(jīng)有別的線程申請成功monitor了,這個時候它就得等著,等別的線程執(zhí)行完synchronized里面的代碼之后就會執(zhí)行monitorexit指令釋放monitor監(jiān)視器,這樣其它在等待的線程就可以再次申請獲取monitor監(jiān)視器了。

monitor又是個啥東西?為什么monitor能當做鎖?首先既然你知道每個對象都有一個monitor監(jiān)視器,那你知道每個對象是怎么和它的monitor監(jiān)視器關(guān)聯(lián)起來的不?

通過synchronized進行加鎖,就是通過對象頭的Mark Word關(guān)聯(lián)起來的,里面記錄著鎖狀態(tài)和占有鎖的線程地址指針。

當Mark Word中最后兩位的鎖標志位是10的時候,Mark Word的前面是monitor監(jiān)視器的地址,我現(xiàn)在就給你畫出來對象頭、Mark Word 和 monitor之間的關(guān)系圖(32位):

二、monitor內(nèi)部結(jié)構(gòu)

monitor叫做對象監(jiān)視器、也叫作監(jiān)視器鎖,JVM規(guī)定了每一個java對象都有一個monitor對象與之對應(yīng),這monitor是JVM幫我們創(chuàng)建的,在底層使用C++實現(xiàn)的。

其實monitor在C++底層也是某個類的對象,那個類就是ObjectMonitor,它擁有的屬性也字段如下:

//結(jié)構(gòu)體如下
ObjectMonitor::ObjectMonitor() {
_header;
_count ; // 非常重要,表示鎖計數(shù)器,_count = 0表示還沒人加鎖,_count > 0 表示加鎖的次數(shù)
_waiters;
_recursions;
_owner; // 非常重要,指向加鎖成功的線程,_owner = null 時候表示沒人加鎖
_waitset; // wait線程的集合,在synchorized代碼塊中調(diào)用wait()方法的線程會被加入到此集合中沉睡,等待別人叫醒它
_waitsetLock;
_responsiable;
_succ;
_cxq;
_freenext;
_entrylist; // 非常重要,等待隊列,加鎖失敗的線程會被加入到這個等待隊列中,等待再次爭搶鎖
_spinFreq; // 獲取鎖之前的自旋的次數(shù)
_spinclock; // 獲取之前每次鎖自旋的時間
ownerIsThread;
}

3.1、monitor加鎖原理

_count : 這個屬性非常重要,直接表示有沒有被加鎖,如果沒被線程加鎖則 _count=0,如果_count大于0則說明被加鎖了

_owner:這個屬性也非常重要,直接指向加鎖的線程,比如線程A獲取鎖成功了,則_owner = 線程A;當_owner = null的時候表示沒線程加鎖

_waitset:當持有鎖的線程調(diào)用wait()方法的時候,那個線程就會釋放鎖,然后線程被加入到monitor的waitset集合中等待,然后線程就會被掛起。只有有別的線程調(diào)用notify將它喚醒。_entrylist:這個就是等待隊列,當線程加鎖失敗的時候被block住,然后線程會被加入到這個entrylist隊列中,等待獲取鎖。

_spinFreq:獲取鎖失敗前自旋的次數(shù);JDK1.6之后對synchronized進行優(yōu)化;原先JDK1.6以前,只要線程獲取鎖失敗,線程立馬被掛起,線程醒來的時候再去競爭鎖,這樣會導(dǎo)致頻繁的上下文切換,性能太差了。JDK1.6后優(yōu)化了這個問題,就是線程獲取鎖失敗之后,不會被立馬掛起,而是每個一段時間都會重試去爭搶一次,這個_spinFreq就是最大的重試次數(shù),也就是自旋的次數(shù),如果超過了這個次數(shù)搶不到,那線程只能沉睡了。_spinClock:上面說獲取鎖失敗每隔一段時間都會重試一次,這個屬性就是自旋間隔的時間周期,比如50ms,那么就是每隔50ms就嘗試一次獲取鎖。

下面通過圖文展示加鎖過程:

(1)首先呢,沒有線程對monitor進行加鎖的時候是這樣的:

說明:_count = 0 表示加鎖次數(shù)是0,也就是沒線程加鎖;_owner 指向null,也就是沒線程加鎖

(2)然后呢,這個時候線程A、線程B來競爭加鎖了,如下圖所示:

(3)線程A競爭到鎖,將_count 修改為1,表示加鎖次數(shù)為1,將_owner = 線程A,也就是指向自己,表示線程A獲取到了鎖。在_count = 0,_owner = null的時候,表示monitor沒人加鎖,這個時候線程A和線程B同時請求加鎖,也就是競爭將_count改為1。由于線程A這哥們動作比較快,它將_count改為1,獲取鎖成功了。它還嘚瑟了一下,同時將_onwer = 線程A,表示自己獲取了鎖,告訴線程B,兄弟不好意思了,是我獲取了鎖,我先去操作了。

既然加鎖就是將_count 設(shè)置為1,同時將_owner 指向自己。那反過來推測,釋放鎖的時候是不是將_count 設(shè)置為 0 , 將 _owner 設(shè)置為 null 就 OK了?是的,釋放鎖的過程就是這么簡單:

加鎖和釋放鎖說完了,我們接下來將的是

_spinFreq、_spinclock、_entrylist

這幾個東西:

上面解釋字段屬性的時候說_spinFreq是等待鎖期間自旋的次數(shù)、_spinclock是自旋的周期也就是每次自旋多久時間、_entrylist這個就是自旋次數(shù)用完了還沒獲取鎖,只能放到_entrylist等待隊列掛起了。

讓我們繼續(xù)接著圖來講:

(1)首先線程B獲取鎖的時候發(fā)現(xiàn)monitor已經(jīng)被線程A加鎖了(2)然后monitor里面記錄的_spinFreq 、spinclock 信息告訴線程B,你可以每隔50ms來嘗試加鎖一次,總共可以嘗試10次(3)如果線程B在10次嘗試加鎖期間,獲取鎖成功了,那線程B將_count 設(shè)置為 1,_owner 指向自己表示自己獲取鎖成功了(4)如果10次嘗試獲取鎖此時都用完了,那沒轍了,它只能放到等待隊列里面先睡覺去了,也就是線程B被掛起了

_spinFreq和_spinclock 這兩個monitor的屬性主要是讓線程自旋的時候使用的吧。

entryList作用是當線程自旋次數(shù)都用完了之后,只能進入等待隊列進行休眠了。

4.6、輕量級鎖

輕量級鎖模式下,加鎖之前會創(chuàng)建一個鎖記錄,然后將Mark Word中的數(shù)據(jù)備份到鎖記錄中(Mark Word存儲hashcode、GC年齡等很重要數(shù)據(jù),不能丟失了),以便后續(xù)恢復(fù)Mark Word使用。這個鎖記錄放在加鎖線程的虛擬機棧中,加鎖的過程就是將Mark Word 前面的30位指向鎖記錄地址。所以mark word的這個地址指向哪個線程的虛擬機棧中,就說明哪個線程獲取了輕量級鎖。就好比下面的圖,線程A獲取了輕量級鎖,鎖記錄存在線程A的虛擬機棧中,然后Mark Word的前面30位存儲鎖記錄的地址。

了解了輕量級加鎖的原理之后,我們繼續(xù),來講講偏向鎖升級為輕量級鎖的過程:

(1)首先線程A持有偏向鎖,然后正在執(zhí)行synchronized塊中的代碼

(2)這個時候線程B來競爭鎖,發(fā)現(xiàn)有人加了偏向鎖并且正在執(zhí)行synchronized塊中的代碼,為了避免上述說的線程A一直持有鎖不釋放的情況,需要對鎖進行升級,升級為輕量級鎖

(3)先將線程A暫停,為線程A創(chuàng)建一個鎖記錄Lock Record,將Mark Word的數(shù)據(jù)復(fù)制到鎖記錄中;然后將鎖記錄放入線程A的虛擬機棧中

(4)然后將Mark Word中的前30位指向線程A中鎖記錄的地址,將線程A喚醒,線程A就知道自己持有了輕量級鎖

4.6.2、在輕量級鎖模式下,多線程是怎么競爭鎖和釋放鎖的?

(1)線程A和線程B同時競爭鎖,在輕量級鎖模式下,都會創(chuàng)建Lock Record鎖記錄放入自己的棧幀中

(2)同時執(zhí)行CAS操作,將Mark Word前30位設(shè)置為自己鎖記錄的地址,誰設(shè)置成功了,鎖就獲取到鎖

上面講了加鎖的過程,輕量級鎖的釋放很簡單,就將自己的Lock Record中的Mark Word備份的數(shù)據(jù)恢復(fù)回去即可,恢復(fù)的時候執(zhí)行的是CAS操作將Mark Word數(shù)據(jù)恢復(fù)成加鎖前的樣子。

Java synchronized偏向鎖后hashcode存在哪里?

jdk8偏向鎖是默認開啟,但是是有延時的,可通過參數(shù): -XX:BiasedLockingStartupDelay=0關(guān)閉延時。

hashcode是懶加載,在調(diào)用hashCode方法后才會保存在對象頭中。

當對象頭中沒有hashcode時,對象頭鎖的狀態(tài)是 可偏向( biasable,101,且無線程id)。

如果在同步代碼塊之前調(diào)用hashCode方法,則對象頭中會有hashcode,且鎖狀態(tài)是 不可偏向(0 01),這時候再執(zhí)行同步代碼塊,鎖直接是 輕量級鎖(thin lock,00)。

如果是在同步代碼塊中執(zhí)行hashcode,則鎖是從 偏向鎖 直接膨脹為 重量級鎖。

責任編輯:武曉燕 來源: 今日頭條
相關(guān)推薦

2021-01-08 08:34:09

Synchronize線程開發(fā)技術(shù)

2024-03-15 15:12:27

關(guān)鍵字底層代碼

2025-03-20 06:48:55

性能優(yōu)化JDK

2022-10-28 10:23:27

Java多線程底層

2017-12-06 16:28:48

Synchronize實現(xiàn)原理

2022-04-13 14:43:05

JVM同步鎖Monitor 監(jiān)視

2024-03-07 07:47:04

代碼塊Monitor

2019-05-27 08:11:13

高并發(fā)Synchronize底層

2024-08-28 08:00:00

2023-01-04 07:54:03

HashMap底層JDK

2023-07-11 08:00:00

2022-12-19 08:00:00

SpringBootWeb開發(fā)

2017-02-27 10:43:07

Javasynchronize

2017-10-23 10:13:18

IO底層虛擬

2020-08-23 10:03:51

SynchronizeJava

2021-07-04 08:01:30

Synchronize線程安全并發(fā)編程

2021-10-26 13:18:52

Go底層函數(shù)

2024-01-29 08:00:00

架構(gòu)微服務(wù)開發(fā)

2023-07-17 08:02:44

ZuulIO反應(yīng)式

2024-03-14 14:56:22

反射Java數(shù)據(jù)庫連接
點贊
收藏

51CTO技術(shù)棧公眾號