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