Java synchronized偏向鎖的概念與使用
一、什么是偏向鎖
HotSpot作者經(jīng)過研究實(shí)踐發(fā)現(xiàn),在大多數(shù)情況下,鎖不僅不存在多線程競爭,而且總是由同一線程多次獲得,為了讓線程獲得鎖的代價(jià)更低,引進(jìn)了偏向鎖。
偏向鎖的“偏”,它的意思是鎖會偏向于第一個(gè)獲得它的線程,會在對象頭(Mark Word中)存儲鎖偏向的線程ID,以后該線程進(jìn)入和退出同步塊時(shí)只需要檢查是否為偏向鎖、鎖標(biāo)志位以及ThreadID即可。
如下圖是偏向鎖對象頭MarkWord布局:
二、偏向鎖原理
偏向鎖總是被第一個(gè)占用它的線程擁有,這個(gè)線程就是鎖的偏向線程。在偏向鎖的MarkWord中,就有一塊區(qū)域用來記錄偏向線程的ID,這個(gè)是在鎖第一次被擁有的時(shí)候記錄的。
因?yàn)橛涗浟似蚓€程ID,那么后續(xù)如果這個(gè)偏向線程進(jìn)入和退出同步代碼塊的時(shí)候,就不需要再次加鎖和解鎖。
偏向鎖再次進(jìn)入同步代碼塊,是如何保證不需要再次加鎖的?
偏向鎖的線程會檢查鎖對象的MarkWord中是不是存放著自己的線程ID。
相等
表示偏向鎖現(xiàn)在就是偏向當(dāng)前線程的,無需再嘗試獲得鎖。以后每次同步,都會檢查鎖的偏向線程ID與當(dāng)前線程ID是否一致,一致就直接進(jìn)入同步,省去了CAS去更新對象頭的操作,提高了鎖的性能。
不相等
表示偏向鎖現(xiàn)在不是偏向當(dāng)前線程的,此時(shí)發(fā)生了競爭,這時(shí)候當(dāng)前線程就會嘗試CAS去更新鎖對象MarkWord中線程ID,嘗試指定為自己的線程。此時(shí)也有兩種情況:
1)、更新成功:表示已經(jīng)將鎖對象MarkWord中的線程ID替換為自己的線程ID,之前的線程可能剛好運(yùn)行完,這時(shí)候鎖重新偏向?yàn)楫?dāng)前線程,這種情況下,不會發(fā)生鎖升級,鎖仍然為偏向鎖,只是偏向線程換成了另外一個(gè)線程;
2)、更新失?。罕硎局暗木€程還在持有鎖,那么當(dāng)前線程只能一直CAS,當(dāng)達(dá)到一定次數(shù)后,如果還沒CAS成功,那么就會發(fā)生【偏向鎖 -> 輕量級鎖】的鎖升級過程。
注意,偏向鎖只有遇到其它線程嘗試競爭偏向鎖時(shí),持有偏向鎖的線程才會釋放鎖,線程不會主動(dòng)釋放偏向鎖的。
三、偏向鎖演示
public class BiasedLockDemo01 { public static void main(String[] args) { Object objLock = new Object(); new Thread(() -> { synchronized (objLock) { System.out.println(ClassLayout.parseInstance(objLock).toPrintable()); } }, "t1").start(); } }
如上我們看到,markword的倒數(shù)三位是000,根據(jù)前面的圖,000表示的是輕量級鎖,此時(shí)只有一個(gè)線程訪問,為什么輸出來的不是偏向鎖標(biāo)識101呢?
原因其實(shí)是偏向鎖在Java 6之后是默認(rèn)啟用的,但在應(yīng)用程序啟動(dòng)幾秒鐘(默認(rèn)延遲4秒)之后才會激活,可以使用 -XX:BiasedLockingStartupDelay=0參數(shù)關(guān)閉延遲,讓其在程序啟動(dòng)時(shí)立刻啟動(dòng)。當(dāng)然為了演示,也可以在程序中休眠5秒,等待偏向鎖激活后。
下面我們添加運(yùn)行時(shí)JVM參數(shù),再次啟動(dòng)程序,觀察內(nèi)存布局:
可以看到關(guān)閉偏向鎖延遲后,當(dāng)前鎖就是偏向鎖了。
四、偏向鎖的處理流程
當(dāng)線程第一次訪問同步塊并獲取鎖時(shí),偏向鎖處理流程如下:
1)、虛擬機(jī)將會把對象頭中的是否偏向鎖標(biāo)志位設(shè)為“1”,即偏向模式;
2)、使用CAS操作把獲取到這個(gè)鎖的線程的ID記錄在對象的Mark Word之中 ,如果CAS操作成功,持有偏向鎖的線程以后每次進(jìn)入這個(gè)鎖相關(guān)的同步塊時(shí),虛擬機(jī)都可以不再進(jìn)行任何同步操作,效率極高;
五、偏向鎖的撤銷
前面提到,大部分情況下,鎖都是被同一個(gè)線程獲取到,持有偏向鎖的線程不會主動(dòng)釋放鎖。那么大部分情況下,不會涉及到偏向鎖的撤銷,當(dāng)有另外的線程嘗試競爭偏向鎖的時(shí)候,這個(gè)時(shí)候才會涉及偏向鎖的撤銷流程。
舉個(gè)例子,有線程A、線程B兩個(gè)線程競爭獲取鎖,鎖大部分情況下都被線程A持有,此時(shí)鎖偏向于線程A,這樣線程A每次進(jìn)入/退出同步代碼塊,都無需再次加鎖,只需判斷MarkWord中保存的線程ID是否等于自己的線程ID。
線程A不會主動(dòng)釋放偏向鎖,當(dāng)運(yùn)行了一段時(shí)間后,突然線程B過來嘗試競爭這個(gè)偏向鎖了,此時(shí)持有偏向鎖的線程A會發(fā)生偏向鎖的撤銷。
偏向鎖的撤銷需要等待全局安全點(diǎn)(這個(gè)時(shí)間點(diǎn)沒有代碼正在執(zhí)行),同時(shí)會檢查持有偏向鎖的線程A是否還在執(zhí)行:
1)、線程A還是同步代碼塊中運(yùn)行:發(fā)生【偏向鎖 -> 輕量級鎖】的升級過程。此時(shí)輕量級鎖由原持有偏向鎖的線程A持有,繼續(xù)執(zhí)行其同步代碼,而正在競爭的線程B會在外面CAS自旋等待獲取這個(gè)輕量級鎖。
2)、線程A剛好執(zhí)行完成同步代碼塊:則會將對象頭MarkWord設(shè)置為無鎖狀態(tài)并撤銷偏向鎖,然后鎖重新偏向到線程B,注意,這里會修改MarkWord中線程ID保存為線程B的線程ID。
流程圖大體如下:
六、偏向鎖的好處
偏向鎖是在只有一個(gè)線程執(zhí)行同步塊時(shí)進(jìn)一步提高性能,適用于一個(gè)線程反復(fù)獲得同一個(gè)鎖的情況。偏向鎖可以提高帶有同步但無競爭的程序性能。它并不一定總是對程序運(yùn)行有利,如果程序中大多數(shù)的鎖總是被多個(gè)不同的線程訪問比如線程池,那偏向模式就是多余的,反而會影響效率。
注意,在JDK15中,已經(jīng)廢棄偏向鎖。目前還是使用JDK1.8居多,所以我們還是有必要了解一下偏向鎖。
到此這篇關(guān)于Java synchronized偏向鎖的概念與使用的文章就介紹到這了,更多相關(guān)Java synchronized偏向鎖內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Jenkins Maven pom jar打包未拉取最新包解決辦法
包版本號未變更新后,jenkins打包不會拉取最新包,本文主要介紹了Jenkins Maven pom jar打包未拉取最新包解決辦法,具有一定的參考價(jià)值,感興趣的可以了解一下2024-02-02Springboot項(xiàng)目的搭建教程(分離出common父依賴)
這篇文章主要介紹了Springboot項(xiàng)目的搭建教程(分離出common父依賴),具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01最新IntelliJ?IDEA?2022配置?Tomcat?8.5?的詳細(xì)步驟演示
這篇文章主要介紹了IntelliJ?IDEA?2022?詳細(xì)配置?Tomcat?8.5?步驟演示,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-08-08詳解SpringBoot中@SessionAttributes的使用
這篇文章主要通過示例為大家詳細(xì)介紹了SpringBoot中@SessionAttributes的使用,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下2022-07-07