Synchronized 不理解之處~~

此文只是記錄了個(gè)人對(duì)Synchronized的了解和認(rèn)知,基本上都是參考網(wǎng)上文獻(xiàn),在了解的過(guò)程中對(duì)Synchronized的原理上產(chǎn)生一些疑問(wèn),經(jīng)過(guò)多番查閱網(wǎng)上并沒(méi)有比較準(zhǔn)確或者相對(duì)權(quán)威的參考資料,疑問(wèn)如下:

1.多線程在競(jìng)爭(zhēng)瑣時(shí)是先進(jìn)入contentionList還是entryList??jī)烧呤鞘裁搓P(guān)系?

2.一個(gè)線程釋放鎖時(shí),是怎么通知阻塞的線程來(lái)競(jìng)爭(zhēng)鎖的?是如何競(jìng)爭(zhēng)的?

3.執(zhí)行完任務(wù)且釋放鎖的線程會(huì)被放到WaitList嗎?為什么?不會(huì)被回收嗎?

有知道的大佬希望能解答一下~

下面是個(gè)人對(duì)Synchronized的理解:
先了解一下幾個(gè)概念

原子性
是指一個(gè)操作或多個(gè)操作要么全部執(zhí)行,且執(zhí)行的過(guò)程不會(huì)被任何因素打斷,要么就都不執(zhí)行。不可中斷的一個(gè)或一系列操作。

可見(jiàn)性
指當(dāng)多個(gè)線程訪問(wèn)同一個(gè)變量時(shí),一個(gè)線程修改了這個(gè)變量的值,其他線程能夠立即看得到修改的值。
通過(guò)volatile關(guān)鍵字修飾內(nèi)存中的變量,該變量在線程之間共享。是輕量級(jí)的鎖(synchronized),消耗的成本比synchronized小很多。volatile用于修飾變量。
如何保證可見(jiàn)性?
對(duì)一個(gè)變量unlock操作之前,必須要同步到主內(nèi)存中;如果對(duì)一個(gè)變量進(jìn)行l(wèi)ock操作,則將會(huì)清空工作內(nèi)存中此變量的值,在執(zhí)行引擎使用此變量前,需要重新從主內(nèi)存中l(wèi)oad操作或assign操作初始化變量值” 來(lái)保證的;

有序性
即程序執(zhí)行的順序按照代碼的先后順序執(zhí)行。

如果在本線程內(nèi)觀察,所有操作都是有序的;如果在一個(gè)線程中觀察另一個(gè)線程,所有操作都是無(wú)序的

Synchronized的使用
修飾實(shí)例方法:鎖是當(dāng)前實(shí)例對(duì)象。
修飾靜態(tài)方法:鎖是當(dāng)前類的class對(duì)象。
修飾代碼塊:鎖是括號(hào)中的對(duì)象。

Java 對(duì)象存儲(chǔ)在內(nèi)存中,分為三個(gè)部分,即對(duì)象頭、實(shí)例數(shù)據(jù)和對(duì)齊填充,而在其對(duì)象頭中,保存了鎖標(biāo)識(shí)。Synchronized的實(shí)現(xiàn)依賴于下對(duì)象頭:

|---------------------------------------------------------------------------------|
|                                 Object Header (96 bits)                         |
|--------------------------------|-----------------------|------------------------|
|        Mark Word(32bits)       |    Klass Word(32bits) |  array length(32bits)  |
|--------------------------------|-----------------------|------------------------|

1.Mark Word(標(biāo)記字段)

用于存儲(chǔ)對(duì)象自身的運(yùn)行時(shí)數(shù)據(jù),如哈希碼(HashCode)、GC分代年齡、鎖狀態(tài)標(biāo)志、線程持有的鎖、偏向線程 ID、偏向時(shí)間戳等等;
Mark Word被設(shè)計(jì)成為一個(gè)非固定的數(shù)據(jù)結(jié)構(gòu),以便存儲(chǔ)更多有效的數(shù)據(jù),它會(huì)根據(jù)對(duì)象本身的狀態(tài)復(fù)用自己的存儲(chǔ)空間,處于不同的狀態(tài),里面的結(jié)構(gòu)也有不同。

a

鎖標(biāo)志位 10 對(duì)應(yīng)的是重量級(jí)鎖,其指針指向的是 Monitor 對(duì)象(管程)的起始地址。

Monitor(管程)Synchronized實(shí)現(xiàn)的關(guān)鍵
Monitor是一種同步工具或機(jī)制,通常被描述成一個(gè)對(duì)象,由
ObjectMonitor.hpp文件,C++實(shí)現(xiàn)的。

ObjectMonitor中關(guān)鍵屬性:

  • _owner: 指向持有ObjectMonitor對(duì)象的線程
  • _WaitSet: 存放處于wait狀態(tài)的線程隊(duì)列
  • _EntryList: 存放處于等待鎖block狀態(tài)的線程隊(duì)列
  • _recursions:記錄owner線程獲取鎖的次數(shù),這也決定了synchronized是可重入的。
    - _count: 一種說(shuō)法是當(dāng)前線程獲取鎖的次數(shù),持有_count+1,釋放_(tái)count-1;另一種說(shuō)法是沒(méi)有獲取到鎖的線程數(shù)量和調(diào)用Sleep方法的線程數(shù)總和。
    synchronized實(shí)現(xiàn)原理
    JVM基于進(jìn)入和退出monitor對(duì)象來(lái)實(shí)現(xiàn)同步,同步代碼塊采用添加moniterenter、moniterexit,同步方法使用ACC_SYNCHRONIZED標(biāo)記符隱式實(shí)現(xiàn)。每個(gè)對(duì)象都有一個(gè)monitor與之關(guān)聯(lián),運(yùn)行到moniterenter時(shí)嘗試獲取對(duì)應(yīng)monitor的所有權(quán),獲取成功就將monitor的進(jìn)入數(shù)加1(所以是可重入鎖,也被稱為重量級(jí)鎖),否則就阻塞,擁有monitor的線程運(yùn)行到moniterexit時(shí)進(jìn)入數(shù)減1,為0時(shí)釋放monitor。
    java中每個(gè)對(duì)象都有一個(gè)對(duì)象頭,synchronized所用的鎖就是存在對(duì)象頭里的。如果是非數(shù)組的對(duì)象是8個(gè)字節(jié)(32位JVM)或者16字節(jié)(64位JVM),數(shù)組對(duì)象還會(huì)有一個(gè)數(shù)組長(zhǎng)度(4個(gè)字節(jié))

當(dāng)多個(gè)線程同時(shí)訪問(wèn)一段同步代碼時(shí)候,首先會(huì)進(jìn)入_EntryList,當(dāng)某個(gè)線程獲取到Monitor后,會(huì)把_owner變量設(shè)置為當(dāng)前線程,同時(shí)_count計(jì)數(shù)器加1,既獲得對(duì)象鎖。
若持有Monitor的線程調(diào)用wait()方法或執(zhí)行完畢,將釋放當(dāng)前持有的Monitor,_owner變量設(shè)為null,_count計(jì)數(shù)器減1,同時(shí)該線程進(jìn)入_WaitSet集合中等待被喚醒。

為什么說(shuō)Synchronized是java語(yǔ)言中一個(gè)重量級(jí)操作?
Java 線程是映射到操作系統(tǒng)原生線程之上的,如果要阻塞或喚醒一個(gè)線程就需要操作系統(tǒng)幫忙,這就要從用戶態(tài)轉(zhuǎn)換到核心態(tài),狀態(tài)的轉(zhuǎn)換需要花費(fèi)處理器很多時(shí)間來(lái)處理,可能比用戶代碼執(zhí)行時(shí)間還要長(zhǎng),所以說(shuō)Synchronized是java語(yǔ)言中一個(gè)重量級(jí)操作。

2.Klass Point(類型指針)

這一部分用于存儲(chǔ)對(duì)象的類型指針,該指針指向它的類元數(shù)據(jù),JVM通過(guò)這個(gè)指針確定對(duì)象是哪個(gè)類的實(shí)例。該指針的位長(zhǎng)度為JVM的一個(gè)字大小,即32位的JVM為32位,64位的JVM為64位。
如果應(yīng)用的對(duì)象過(guò)多,使用64位的指針將浪費(fèi)大量?jī)?nèi)存,統(tǒng)計(jì)而言,64位的JVM將會(huì)比32位的JVM多耗費(fèi)50%的內(nèi)存。為了節(jié)約內(nèi)存可以使用選項(xiàng)+UseCompressedOops開(kāi)啟指針壓縮,其中,oop即ordinary object pointer普通對(duì)象指針。開(kāi)啟該選項(xiàng)后,下列指針將壓縮至32位:

每個(gè)Class的屬性指針(即靜態(tài)變量)
每個(gè)對(duì)象的屬性指針(即對(duì)象變量)
普通對(duì)象數(shù)組的每個(gè)元素指針
當(dāng)然,也不是所有的指針都會(huì)壓縮,一些特殊類型的指針JVM不會(huì)優(yōu)化,比如指向PermGen的Class對(duì)象指針(JDK8中指向元空間的Class對(duì)象指針)、本地變量、堆棧元素、入?yún)ⅰ⒎祷刂岛蚇ULL指針等。

3.array length
如果對(duì)象是一個(gè)數(shù)組,那么對(duì)象頭還需要有額外的空間用于存儲(chǔ)數(shù)組的長(zhǎng)度,這部分?jǐn)?shù)據(jù)的長(zhǎng)度也隨著JVM架構(gòu)的不同而不同:32位的JVM上,長(zhǎng)度為32位;64位JVM則為64位。64位JVM如果開(kāi)啟+UseCompressedOops選項(xiàng),該區(qū)域長(zhǎng)度也將由64位壓縮至32位。

為什么“wait/notify”方法必須要在synchronized的方法或者段中調(diào)用呢?
因?yàn)橹挥羞M(jìn)入了synchronized方法或者方法段中的線程才擁有了該鎖對(duì)象,只有擁有了該鎖對(duì)象才能調(diào)用該鎖對(duì)象的wait和notify方法。

此文只是記錄了個(gè)人對(duì)Synchronized的了解和認(rèn)知,基本上都是參考網(wǎng)上文獻(xiàn),在了解的過(guò)程中對(duì)Synchronized的原理上產(chǎn)生一些疑問(wèn),經(jīng)過(guò)多番查閱網(wǎng)上并沒(méi)有比較準(zhǔn)確或者相對(duì)權(quán)威的參考資料,疑問(wèn)如下:

1.多線程在競(jìng)爭(zhēng)瑣時(shí)是先進(jìn)入contentionList還是entryList??jī)烧呤鞘裁搓P(guān)系?

2.一個(gè)線程釋放鎖時(shí),是怎么通知阻塞的線程來(lái)競(jìng)爭(zhēng)鎖的?是如何競(jìng)爭(zhēng)的?

3.執(zhí)行完任務(wù)且釋放鎖的線程會(huì)被放到WaitList嗎?為什么?不會(huì)被回收嗎?

有知道的大佬希望能解答一下~

參考 對(duì)象頭

?著作權(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)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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