溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Java中常用的鎖機制

發(fā)布時間:2020-07-11 13:06:37 來源:網(wǎng)絡 閱讀:311 作者:淺嫣 欄目:開發(fā)技術

Java中常用的鎖機制

1.鎖的定義和鎖粒度

1.1什么是鎖?

      在計算機科學中,鎖(lock)或互斥(mutex)是一種同步機制,用于在有許多執(zhí)行線程的環(huán)境中強制對資源的訪問限制。鎖旨在強制實施互斥排他、并發(fā)控制策略。

      鎖通常需要硬件支持才能有效實施。這種支持通常采取一個或多個原子指令的形式,如"test-and-set", "fetch-and-add" or "compare-and-swap"”。這些指令允許單個進程測試鎖是否空閑,如果空閑,則通過單個原子操作獲取鎖。

1.2.鎖的一個重要屬性 粒度 Granularity [gr?nj?‘l?r?t?]

在引入鎖粒度之前,需要了解關于鎖的三個概念:

    1、鎖開銷 lock overhead 鎖占用內存空間、 cpu初始化和銷毀鎖、獲取和釋放鎖的時間。程序使用的鎖越多,相應的鎖開銷越大

    2、鎖競爭 lock contention 一個進程或線程試圖獲取另一個進程或線程持有的鎖,就會發(fā)生鎖競爭。鎖粒度越小,發(fā)生鎖競爭的可能性就越小

    3、死鎖 deadlock 至少兩個任務中的每一個都等待另一個任務持有的鎖的情況鎖粒度是衡量鎖保護的數(shù)據(jù)量大小,通常選擇粗粒度的鎖(鎖的數(shù)量少,每個鎖保護大量的數(shù)據(jù)),在當單進程訪問受保護的數(shù)據(jù)時鎖開銷小,但是當多個進程同時訪問時性能很差。因為增大了鎖的競爭。相反,使用細粒度的鎖(鎖數(shù)量多,每個鎖保護少量的數(shù)據(jù))增加了鎖的開銷但是減少了鎖競爭。例如數(shù)據(jù)庫中,鎖的粒度有表鎖、頁鎖、行鎖、字段鎖、字段的一部分鎖

相關術語  Critical Section(臨界區(qū))、 Mutex/mutual exclusion(互斥體)、 Semaphore/binary semaphore(信號量)


2.鎖的種類

    2.1.獨享鎖/共享鎖

            1)獨享鎖是指該鎖一次只能被一個線程所持有。 (ReentrantLock、 Synchronized)

            2)共享鎖是指該鎖可被多個線程所持有。 (ReadWriteLock)

            3)互斥鎖/讀寫鎖

            獨享鎖/共享鎖這是廣義上的說法,互斥鎖/讀寫鎖就分別對應具體的實現(xiàn)。在Java中如ReentrantLock就是互斥鎖(獨享鎖), ReadWriteLock就是讀             寫 鎖(共享鎖)。 獨享鎖與共享鎖也是通過AQS來實現(xiàn)的

                鎖升級:讀鎖到寫鎖 (不支持)

                鎖降級:寫鎖到讀鎖 (支持)

            4)讀寫鎖 ReentrantReadWriteLock

    高16位代表寫鎖,低16位代表讀鎖

Java中常用的鎖機制

2.2.公平鎖/非公平鎖

1)公平鎖:     是指多個線程按照申請鎖的順序來獲取鎖。

2)非公平鎖 : 是指多個線程獲取鎖的順序并不是按照申請鎖的順序,有可能后申請的線程比先申請的線程優(yōu)先獲取鎖。有可能會造成饑餓現(xiàn)象。

     對于Java ReentrantLock而言,通過構造函數(shù)指定該鎖是否是公平鎖,默認是非公平鎖。非公平鎖的優(yōu)點在于吞吐量比公平鎖大。

     對于Synchronized而言,也是一種非公平鎖。由于其并不像ReentrantLock是通過AQS的控制線程對鎖的獲取, 所以并沒有任何辦法使其變成公平鎖。

Java中常用的鎖機制

2.3.可重入鎖

可重入鎖,又名,遞歸鎖,是指同一個線程在外層方法獲取鎖的時候,在進入內層方法會自動獲取

鎖。

ReentrantLock和Synchronized都是可重入鎖。可重入鎖的一個好處是可一定程度避免死鎖
Java中常用的鎖機制

如上面的代碼,如果synchronized不是可重入鎖的話,testB就不會被當前線程執(zhí)行,從而形成死鎖。

需要注意的是,可重入鎖   加鎖和解鎖的次數(shù)要相等。

Java中常用的鎖機制

C==0表明未獲得鎖,Else表示已經(jīng)獲得鎖,這時對state加1,相應的,每次釋放鎖都會對state減1

2.4.樂觀鎖/悲觀鎖

樂觀鎖/悲觀鎖不是指具體類型的鎖,而是看待并發(fā)的角度。

悲觀鎖認為存在很多并發(fā)更新操作,采取加鎖操作,如果不加鎖一定會有問題

樂觀鎖認為不存在很多的并發(fā)更新操作,不需要加鎖。數(shù)據(jù)庫中樂觀鎖的實現(xiàn)一般采用版本號,Java中可使用CAS實現(xiàn)樂觀鎖。

2.5.分段鎖

分段鎖是一種鎖的設計,并不是一種具體的鎖。對于ConcuttentHashMap就是通過分段鎖實現(xiàn)高效的并發(fā)操作。

Java中常用的鎖機制

2.6.自旋鎖

自旋鎖是指嘗試獲取鎖的線程不會阻塞,而是采用循環(huán)的方式嘗試獲取鎖。好處是減少上下文切換,缺點是一直占用CPU資源。

Java中常用的鎖機制

2.7.偏向鎖/輕量級鎖/重量級鎖

這是jdk1.6中對Synchronized鎖做的優(yōu)化,首先了解下對象頭(Mark Word):

運行時JVM內存布局

Java中常用的鎖機制

Mark Word在不同鎖狀態(tài)下的標志位存儲

Java中常用的鎖機制

從jdk1.6開始為了減少獲得鎖和釋放鎖帶來的性能消耗,引入了“偏向鎖”和“輕量級鎖”。

鎖共有四種狀態(tài),級別從低到高分別是:無鎖狀態(tài)、偏向鎖狀態(tài)、輕量級鎖狀態(tài)和重量級鎖狀態(tài)。隨著競爭情況鎖狀態(tài)逐漸升級、鎖可以升級但不能降級


偏向鎖的獲取和撤銷:

HotSpot作者經(jīng)過研究發(fā)現(xiàn),大多數(shù)情況下,鎖不僅不存在多線程競爭,而且總是由同一線程多次獲得,為了讓線程獲得鎖的代價更低而引入偏向鎖。

線程1檢查對象頭中的Mark Word中是否存儲了線程1,如果沒有則CAS操作將Mark Word中的線程ID替換為線程1。此時,鎖偏向線程1,后面該線程進入同步塊時不需要進行CAS操作,只需要簡單的測試一下Mark Word中是否存儲指向當前線程的偏向鎖,如果成功表明該線程已經(jīng)獲得鎖。如果失敗,則再需要測試一下Mark Word中偏向鎖標識是否設置為1(是否是偏向鎖),如果沒有設置,則使用CAS競爭鎖,如果設置了,則嘗試使用CAS將偏向鎖指向當前線程

  Java中常用的鎖機制

偏向鎖的競爭結果:

根據(jù)持有偏向鎖的線程是否存活

1.如果不活動,偏向鎖撤銷到無鎖狀態(tài),再偏向到其他線程
2.如果線程仍然活著,則升級到輕量級鎖

偏向鎖在Java6和Java7中默認是開啟的,但是在應用程序啟動幾秒后才激活,如果有必要可以關閉延遲:
-XX:BiasedLockingStartupDelay=0

如果確定應用程序中所有的鎖通常情況下處于競爭狀態(tài),可以通過JVM參數(shù)關閉偏向鎖:
-XX:-UseBiasedLocking=false,那么程序默認會進入輕量級鎖。

-XX:BiasedLockingStartupDelay=0 -XX:+TraceBiasedLocking

輕量級鎖膨脹:

1.線程在執(zhí)行同步塊之前,JVM會在當前棧楨中創(chuàng)建用于存儲鎖記錄的空間(Lock record),并將對象頭中的Mark Word復制到鎖記錄中(Displaced Mark Word)。
2.然后線程嘗試使用CAS將對象頭中的Mark Word替換為指向鎖記錄的指針
3.如果成功,當前線程獲得鎖,如果失敗,表示其他線程競爭鎖,當前線程嘗試使用自旋來獲取鎖

Java中常用的鎖機制

偏向鎖、輕量級鎖、重量級鎖的優(yōu)缺點

Java中常用的鎖機制

1.偏向鎖是為了避免某個線程反復獲得/釋放同一把鎖時的性能消耗,如果仍然是同個線程去獲得這個鎖,嘗試偏向鎖時會直接進入同步塊,不需要再次獲得鎖。

2.而輕量級鎖和自旋鎖都是為了避免直接調用操作系統(tǒng)層面的互斥操作,因為掛起線程是一個很耗資源的操作。

為了盡量避免使用重量級鎖(操作系統(tǒng)層面的互斥),首先會嘗試輕量級鎖,輕量級鎖會嘗試使用CAS操作來獲得鎖,如果輕量級鎖獲得失敗,說明存在競爭。但是也許很快就能獲得鎖,就會嘗試自旋鎖,將線程做幾個空循環(huán),每次循環(huán)時都不斷嘗試獲得鎖。如果自旋鎖也失敗,那么只能升級成重量級鎖。

3.可見偏向鎖,輕量級鎖,自旋鎖都是樂觀鎖。

逃逸分析:

Java中常用的鎖機制


逃逸分析:通俗一點講,當一個對象的指針被多個方法或線程引用時,我們稱這個指針發(fā)生了逃逸,必須在JIT里完成

鎖粗化:

如果虛擬機探測到有這樣一串零碎的操作都對同一個對象加鎖,將會把加鎖同步的范圍擴展到整個操作序列的外部,這樣就只需要加鎖一次就夠了

鎖消除:

如果你定義的類的方法上有同步鎖,但在運行時,卻只有一個線程在訪問,此時逃逸分析后的機器碼,會去掉同步鎖運行。

棧上分配:

分析找到未逃逸的變量,將變量類的實例化內存直接在棧里分配(無需進入堆),分配完成后,繼續(xù)在調用棧內執(zhí)行,最后線程結束,??臻g被回收,局部變量對象也被回收。

從jdk1.6開始默認開啟:
開啟:  -XX:+DoEscapeAnalysis

關閉:  -XX:-DoEscapeAnalysis


3.1.Synchronized與ReentrantLock的區(qū)別

Java中常用的鎖機制

從字節(jié)碼角度看實例synchronized方法、靜態(tài)synchronized方法、synchronized代碼塊實現(xiàn)的不同

Java中常用的鎖機制Java中常用的鎖機制

Java中常用的鎖機制

Java中常用的鎖機制

ReentrantLock =  一個AQS同步器(維護同步狀態(tài)) + 一個AQS同步隊列 + 多個Condition等待隊列

3.2 ReentrantLock繼承體系類圖

Java中常用的鎖機制

ReentrantLock#lock()方法時序圖

Java中常用的鎖機制

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內容。

AI