久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

線程安全(中)

 云澳 2019-09-30

接觸過(guò)線程安全的同學(xué)想必都使用過(guò)synchronized這個(gè)關(guān)鍵字,,在java同步代碼快中,,synchronized的使用方式無(wú)非有兩個(gè):

  1. 通過(guò)對(duì)一個(gè)對(duì)象進(jìn)行加鎖來(lái)實(shí)現(xiàn)同步,如下面代碼,。
synchronized(lockObject){
    //代碼

}
  1. 對(duì)一個(gè)方法進(jìn)行synchronized聲明,,進(jìn)而對(duì)一個(gè)方法進(jìn)行加鎖來(lái)實(shí)現(xiàn)同步。如下面代碼
public synchornized void test(){
    //代碼
}

但這里需要指出的是,,無(wú)論是對(duì)一個(gè)對(duì)象進(jìn)行加鎖還是對(duì)一個(gè)方法進(jìn)行加鎖,,實(shí)際上,都是對(duì)對(duì)象進(jìn)行加鎖,。

也就是說(shuō),,對(duì)于方式2,實(shí)際上虛擬機(jī)會(huì)根據(jù)synchronized修飾的是實(shí)例方法還是類方法,,去取對(duì)應(yīng)的實(shí)例對(duì)象或者Class對(duì)象來(lái)進(jìn)行加鎖,。

對(duì)于synchronized這個(gè)關(guān)鍵字,可能之前大家有聽(tīng)過(guò),,他是一個(gè)重量級(jí)鎖,開(kāi)銷很大,,建議大家少用點(diǎn),。但大家可能也聽(tīng)說(shuō)過(guò),但到了jdk1.6之后,該關(guān)鍵字被進(jìn)行了很多的優(yōu)化,,已經(jīng)不像以前那樣不給力了,,建議大家多使用。

那么它是進(jìn)行了什么樣的優(yōu)化,,才使得synchronized又深得人心呢,?為何重量級(jí)鎖開(kāi)銷就大呢?

想必大家也都聽(tīng)說(shuō)過(guò)輕量級(jí)鎖,,重量級(jí)鎖,,自旋鎖,自適應(yīng)自旋鎖,,偏向鎖等等,,他們都有哪些區(qū)別呢?
剛才和大家說(shuō),,鎖是加在對(duì)象上的,,那么一個(gè)線程是如何知道這個(gè)對(duì)象被加了鎖呢?又是如何知道它加的是什么類型的鎖呢,?

基于這些問(wèn)題,,下面我講一步一步講解synchronized是如何被優(yōu)化的,是如何從偏向鎖到重量級(jí)鎖的,。

鎖對(duì)象

剛才我們說(shuō),,鎖實(shí)際上是加在對(duì)象上的,那么被加了鎖的對(duì)象我們稱之為鎖對(duì)象,,在java中,,任何一個(gè)對(duì)象都能成為鎖對(duì)象。
為了讓大家更好著理解虛擬機(jī)是如何知道這個(gè)對(duì)象就是一個(gè)鎖對(duì)象的,,我們下面簡(jiǎn)單介紹一下java中一個(gè)對(duì)象的結(jié)構(gòu),。
java對(duì)象在內(nèi)存中的存儲(chǔ)結(jié)構(gòu)主要有一下三個(gè)部分:

  1. 對(duì)象頭
  2. 實(shí)例數(shù)據(jù)
  3. 填充數(shù)據(jù)
    這里強(qiáng)調(diào)一下,對(duì)象頭里的數(shù)據(jù)主要是一些運(yùn)行時(shí)的數(shù)據(jù),。
    其簡(jiǎn)單的結(jié)構(gòu)如下
長(zhǎng)度內(nèi)容說(shuō)明
32/64bit Mark Work hashCode,GC分代年齡,,鎖信息
32/64bit Class Metadata Address 指向?qū)ο箢愋蛿?shù)據(jù)的指針
32/64bit Array Length 數(shù)組的長(zhǎng)度(當(dāng)對(duì)象為數(shù)組時(shí))

從該表格中我們可以看到,對(duì)象中關(guān)于鎖的信息是存在Markword里的,。
我們來(lái)看一段代碼

LockObject lockObject = new LockObject();//隨便創(chuàng)建一個(gè)對(duì)象
synchronized(lockObject){
    //代碼
}

當(dāng)我們創(chuàng)建一個(gè)對(duì)象LockObject時(shí),,該對(duì)象的部分Markword關(guān)鍵數(shù)據(jù)如下。

bit fields是否偏向鎖鎖標(biāo)志位
hash 0 01

從圖中可以看出,,偏向鎖的標(biāo)志位是“01”,,狀態(tài)是“0”,表示該對(duì)象還沒(méi)有被加上偏向鎖,。(“1”是表示被加上偏向鎖),。該對(duì)象被創(chuàng)建出來(lái)的那一刻,,就有了偏向鎖的標(biāo)志位,這也說(shuō)明了所有對(duì)象都是可偏向的,,但所有對(duì)象的狀態(tài)都為“0”,,也同時(shí)說(shuō)明所有被創(chuàng)建的對(duì)象的偏向鎖并沒(méi)有生效。

偏向鎖

不過(guò),,當(dāng)線程執(zhí)行到臨界區(qū)(critical section)時(shí),,此時(shí)會(huì)利用CAS(Compare and Swap)操作,將線程ID插入到Markword中,,同時(shí)修改偏向鎖的標(biāo)志位,。

所謂臨界區(qū),就是只允許一個(gè)線程進(jìn)去執(zhí)行操作的區(qū)域,,即同步代碼塊,。CAS是一個(gè)原子性操作

此時(shí)的Mark word的結(jié)構(gòu)信息如下:

bit fields 是否偏向鎖鎖標(biāo)志位
threadId epoch 1 01

此時(shí)偏向鎖的狀態(tài)為“1”,說(shuō)明對(duì)象的偏向鎖生效了,,同時(shí)也可以看到,,哪個(gè)線程獲得了該對(duì)象的鎖。

那么,,什么是偏向鎖?

偏向鎖是jdk1.6引入的一項(xiàng)鎖優(yōu)化,,其中的“偏”是偏心的偏。它的意思就是說(shuō),,這個(gè)鎖會(huì)偏向于第一個(gè)獲得它的線程,,在接下來(lái)的執(zhí)行過(guò)程中,假如該鎖沒(méi)有被其他線程所獲取,,沒(méi)有其他線程來(lái)競(jìng)爭(zhēng)該鎖,,那么持有偏向鎖的線程將永遠(yuǎn)不需要進(jìn)行同步操作。
也就是說(shuō):
在此線程之后的執(zhí)行過(guò)程中,,如果再次進(jìn)入或者退出同一段同步塊代碼,,并不再需要去進(jìn)行加鎖或者解鎖操作,而是會(huì)做以下的步驟:

  1. Load-and-test,,也就是簡(jiǎn)單判斷一下當(dāng)前線程id是否與Markword當(dāng)中的線程id是否一致.
  2. 如果一致,,則說(shuō)明此線程已經(jīng)成功獲得了鎖,繼續(xù)執(zhí)行下面的代碼.
  3. 如果不一致,,則要檢查一下對(duì)象是否還是可偏向,,即“是否偏向鎖”標(biāo)志位的值。
  4. 如果還未偏向,,則利用CAS操作來(lái)競(jìng)爭(zhēng)鎖,,也即是第一次獲取鎖時(shí)的操作。

如果此對(duì)象已經(jīng)偏向了,,并且不是偏向自己,,則說(shuō)明存在了競(jìng)爭(zhēng),。此時(shí)可能就要根據(jù)另外線程的情況,可能是重新偏向,,也有可能是做偏向撤銷,但大部分情況下就是升級(jí)成輕量級(jí)鎖了,。
可以看出,,偏向鎖是針對(duì)于一個(gè)線程而言的,線程獲得鎖之后就不會(huì)再有解鎖等操作了,,這樣可以省略很多開(kāi)銷,。假如有兩個(gè)線程來(lái)競(jìng)爭(zhēng)該鎖話,那么偏向鎖就失效了,,進(jìn)而升級(jí)成輕量級(jí)鎖了,。
為什么要這樣做呢?因?yàn)榻?jīng)驗(yàn)表明,,其實(shí)大部分情況下,,都會(huì)是同一個(gè)線程進(jìn)入同一塊同步代碼塊的。這也是為什么會(huì)有偏向鎖出現(xiàn)的原因,。
在Jdk1.6中,,偏向鎖的開(kāi)關(guān)是默認(rèn)開(kāi)啟的,適用于只有一個(gè)線程訪問(wèn)同步塊的場(chǎng)景,。

鎖膨脹

剛才說(shuō)了,,當(dāng)出現(xiàn)有兩個(gè)線程來(lái)競(jìng)爭(zhēng)鎖的話,那么偏向鎖就失效了,,此時(shí)鎖就會(huì)膨脹,,升級(jí)為輕量級(jí)鎖。這也是我們經(jīng)常所說(shuō)的鎖膨脹

鎖撤銷

由于偏向鎖失效了,,那么接下來(lái)就得把該鎖撤銷,,鎖撤銷的開(kāi)銷花費(fèi)還是挺大的,其大概的過(guò)程如下:

  1. 在一個(gè)安全點(diǎn)停止擁有鎖的線程,。
  2. 遍歷線程棧,,如果存在鎖記錄的話,需要修復(fù)鎖記錄和Markword,,使其變成無(wú)鎖狀態(tài),。
  3. 喚醒當(dāng)前線程,將當(dāng)前鎖升級(jí)成輕量級(jí)鎖,。
    所以,,如果某些同步代碼塊大多數(shù)情況下都是有兩個(gè)及以上的線程競(jìng)爭(zhēng)的話,那么偏向鎖就會(huì)是一種累贅,,對(duì)于這種情況,,我們可以一開(kāi)始就把偏向鎖這個(gè)默認(rèn)功能給關(guān)閉

輕量級(jí)鎖

鎖撤銷升級(jí)為輕量級(jí)鎖之后,,那么對(duì)象的Markword也會(huì)進(jìn)行相應(yīng)的的變化。下面先簡(jiǎn)單描述下鎖撤銷之后,,升級(jí)為輕量級(jí)鎖的過(guò)程:

  1. 線程在自己的棧楨中創(chuàng)建鎖記錄 LockRecord。
  2. 將鎖對(duì)象的對(duì)象頭中的MarkWord復(fù)制到線程的剛剛創(chuàng)建的鎖記錄中,。
  3. 將鎖記錄中的Owner指針指向鎖對(duì)象,。
  4. 將鎖對(duì)象的對(duì)象頭的MarkWord替換為指向鎖記錄的指針,。

對(duì)應(yīng)的圖描述如下(圖來(lái)自周志明深入java虛擬機(jī))
圖片1
圖片2

之后Markwork如下:

bit fields鎖標(biāo)志位
指向LockRecord的指針 00

注:鎖標(biāo)志位”00”表示輕量級(jí)鎖
輕量級(jí)鎖主要有兩種

  1. 自旋鎖
  2. 自適應(yīng)自旋鎖
自旋鎖

所謂自旋,,就是指當(dāng)有另外一個(gè)線程來(lái)競(jìng)爭(zhēng)鎖時(shí),,這個(gè)線程會(huì)在原地循環(huán)等待,,而不是把該線程給阻塞,,直到那個(gè)獲得鎖的線程釋放鎖之后,這個(gè)線程就可以馬上獲得鎖的,。
注意,鎖在原地循環(huán)的時(shí)候,,是會(huì)消耗cpu的,就相當(dāng)于在執(zhí)行一個(gè)啥也沒(méi)有的for循環(huán),。
所以,輕量級(jí)鎖適用于那些同步代碼塊執(zhí)行的很快的場(chǎng)景,,這樣,,線程原地等待很短很短的時(shí)間就能夠獲得鎖了。
經(jīng)驗(yàn)表明,,大部分同步代碼塊執(zhí)行的時(shí)間都是很短很短的,,也正是基于這個(gè)原因,,才有了輕量級(jí)鎖這么個(gè)東西,。

自旋鎖的一些問(wèn)題
  1. 如果同步代碼塊執(zhí)行的很慢,,需要消耗大量的時(shí)間,那么這個(gè)時(shí)侯,,其他線程在原地等待空消耗cpu,,這會(huì)讓人很難受。
  2. 本來(lái)一個(gè)線程把鎖釋放之后,,當(dāng)前線程是能夠獲得鎖的,但是假如這個(gè)時(shí)候有好幾個(gè)線程都在競(jìng)爭(zhēng)這個(gè)鎖的話,,那么有可能當(dāng)前線程會(huì)獲取不到鎖,,還得原地等待繼續(xù)空循環(huán)消耗cup,甚至有可能一直獲取不到鎖,。

基于這個(gè)問(wèn)題,,我們必須給線程空循環(huán)設(shè)置一個(gè)次數(shù),當(dāng)線程超過(guò)了這個(gè)次數(shù),,我們就認(rèn)為,,繼續(xù)使用自旋鎖就不適合了,此時(shí)鎖會(huì)再次膨脹,,升級(jí)為重量級(jí)鎖,。
默認(rèn)情況下,,自旋的次數(shù)為10次,,用戶可以通過(guò)-XX:PreBlockSpin來(lái)進(jìn)行更改,。

自旋鎖是在JDK1.4.2的時(shí)候引入的

自適應(yīng)自旋鎖

所謂自適應(yīng)自旋鎖就是線程空循環(huán)等待的自旋次數(shù)并非是固定的,而是會(huì)動(dòng)態(tài)著根據(jù)實(shí)際情況來(lái)改變自旋等待的次數(shù),。
其大概原理是這樣的:
假如一個(gè)線程1剛剛成功獲得一個(gè)鎖,當(dāng)它把鎖釋放了之后,,線程2獲得該鎖,,并且線程2在運(yùn)行的過(guò)程中,,此時(shí)線程1又想來(lái)獲得該鎖了,,但線程2還沒(méi)有釋放該鎖,,所以線程1只能自旋等待,,但是虛擬機(jī)認(rèn)為,由于線程1剛剛獲得過(guò)該鎖,,那么虛擬機(jī)覺(jué)得線程1這次自旋也是很有可能能夠再次成功獲得該鎖的,,所以會(huì)延長(zhǎng)線程1自旋的次數(shù)
另外,,如果對(duì)于某一個(gè)鎖,,一個(gè)線程自旋之后,,很少成功獲得該鎖,那么以后這個(gè)線程要獲取該鎖時(shí),,是有可能直接忽略掉自旋過(guò)程,,直接升級(jí)為重量級(jí)鎖的,,以免空循環(huán)等待浪費(fèi)資源。

輕量級(jí)鎖也被稱為非阻塞同步,、樂(lè)觀鎖,,因?yàn)檫@個(gè)過(guò)程并沒(méi)有把線程阻塞掛起,,而是讓線程空循環(huán)等待,,串行執(zhí)行。

重量級(jí)鎖

輕量級(jí)鎖膨脹之后,,就升級(jí)為重量級(jí)鎖了。重量級(jí)鎖是依賴對(duì)象內(nèi)部的monitor鎖來(lái)實(shí)現(xiàn)的,,而monitor又依賴操作系統(tǒng)的MutexLock(互斥鎖)來(lái)實(shí)現(xiàn)的,,所以重量級(jí)鎖也被成為互斥鎖
當(dāng)輕量級(jí)所經(jīng)過(guò)鎖撤銷等步驟升級(jí)為重量級(jí)鎖之后,,它的Markword部分?jǐn)?shù)據(jù)大體如下

bit fields鎖標(biāo)志位
指向Mutex的指針 10
為什么說(shuō)重量級(jí)鎖開(kāi)銷大呢

主要是,當(dāng)系統(tǒng)檢查到鎖是重量級(jí)鎖之后,,會(huì)把等待想要獲得鎖的線程進(jìn)行阻塞,,被阻塞的線程不會(huì)消耗cup。但是阻塞或者喚醒一個(gè)線程時(shí),,都需要操作系統(tǒng)來(lái)幫忙,,這就需要從用戶態(tài)轉(zhuǎn)換到內(nèi)核態(tài),,而轉(zhuǎn)換狀態(tài)是需要消耗很多時(shí)間的,有可能比用戶執(zhí)行代碼的時(shí)間還要長(zhǎng),。
這就是說(shuō)為什么重量級(jí)線程開(kāi)銷很大的,。

互斥鎖(重量級(jí)鎖)也稱為阻塞同步,、悲觀鎖

總結(jié)

通過(guò)上面的分析,,我們知道了為什么synchronized關(guān)鍵字為何又深得人心,,也知道了鎖的演變過(guò)程,。
也就是說(shuō),,synchronized關(guān)鍵字并非一開(kāi)始就該對(duì)象加上重量級(jí)鎖,也是從偏向鎖,,輕量級(jí)鎖,再到重量級(jí)鎖的過(guò)程,。
這個(gè)過(guò)程也告訴我們,假如我們一開(kāi)始就知道某個(gè)同步代碼塊的競(jìng)爭(zhēng)很激烈,、很慢的話,,那么我們一開(kāi)始就應(yīng)該使用重量級(jí)鎖了,,從而省掉一些鎖轉(zhuǎn)換的開(kāi)銷。
講到這里就大概完了,,希望能對(duì)你有所幫助


參考資料

  1. 深入理解java虛擬機(jī)(JVM高級(jí)特性與最佳實(shí)踐)
  2. java并發(fā)編程
  3. Eliminating Synchronization Related Atomic Operations with Biased Locking and Bulk Rebiasing

關(guān)注我的公眾號(hào):苦逼的碼農(nóng),,獲取更多原創(chuàng)文章,,后臺(tái)回復(fù)”禮包”送你一份特別的資源大禮包,。

個(gè)人博客

?

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式,、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙,。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào),。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多