JVM到底是怎麼處理鎖的?怎麼不讓我阻塞呢?

開篇:

我是一個線程,生活在JVM(Java虛擬機)中, 這一段日子過得有些無聊,整個世界似乎只有這一個人,天天忙著執行代碼,想休息一下都很難。

我聽說人類寫的代碼中有些特殊的地方,叫做臨界區,比如synchronized修飾的方法或者代碼塊,他們非常神奇,在同一時刻JVM老大隻允許一個線程進入執行。

實際上,老大設置了一把鎖,搶到了這把鎖就可以執行,否則只能阻塞,等待別人釋放鎖。

JVM到底是怎麼處理鎖的?怎麼不讓我阻塞呢?

老大說,阻塞就是不用幹活了,老老實實地等著就行。

竟然還有這等美事! 趕緊讓我阻塞一次吧。

可是老大又說:“每次設置鎖我都得和操作系統打交道,請他在內核中維護一個什麼Mutex(互斥量)的東西,他還得把你們這些線程阻塞,切換,這可是一筆巨大的費用啊,所以這些鎖還是少用為妙。”

我運氣也不好,我不知道執行了多少代碼,調用了多少函數,竟然一次也沒遇到臨界區!

我想也許這個程序員編程時不小心,沒有考慮多線程併發的情況; 也有可能是這些程序大部分都是無狀態的,多少個線程執行都沒有問題。

於是我只好一直執行下去, 不知道過了多少天,我激動地發現,一個synchronized修飾的代碼塊終於出現了:

  1. Account account = ...
  2. synchronized(account){
  3. ...臨界區的代碼...
  4. }

偏向鎖

我滿心期望別的線程已經進入了代碼塊,那我就可以阻塞、休息。

即使沒有其他線程進入臨界區,老大為我申請鎖, 也得和操作系統協商什麼互斥量,從用戶態進入核心態,再從核心態返回用戶態,總要花些功夫吧。

可是老大根本沒有去找操作系統, 只是看了看這個account對象的所謂“對象頭”,其中有個叫做Mark Word的東西,似乎是個什麼數據結構, 裡邊有幾個標識位,還有其他數據。

JVM到底是怎麼處理鎖的?怎麼不讓我阻塞呢?

老大隨手使用CAS操作把我的線程ID記錄到了這個Mark Word當中,修改了標識位,然後告訴我說: 可以了,你現在擁有這把鎖了,進去執行代碼吧。

JVM到底是怎麼處理鎖的?怎麼不讓我阻塞呢?

我驚奇地說:“老大你不和操作系統協商設置Mutex了?”

老大說:“不用了,你看現在就你一個線程進入了這個代碼塊,我只要記錄下你的線程ID,就表示你擁有這把鎖了,不用操作系統介入。”

我獲得了鎖,開始執行被synchronized包裹的代碼塊。

等到我第二次執行到這個synchronized的時候,老大隻是看了一眼鎖對象account的Mark Word就說:“你的線程ID還在,還持有著這個對象的鎖,進入臨界區執行吧。”

我連喘口氣的機會都沒有,只好繼續執行。

老大說,這叫偏向鎖,在沒有別的線程競爭的時候,一直偏向我,可以讓我一直執行下去。

我是多麼期盼來一個新的線程來和我競爭啊!

輕量級鎖

很快,機會就來了。

另外一個線程0x3704也要進入這個代碼塊執行,但是鎖對象account 保存的是我的線程ID,他是沒法進入臨界區的。

我心想,我們兩個至少得有一個進入阻塞狀態,休息一會兒了。

但是老大還是不去操作系統協商,只是說: 我把這個偏向鎖升級一下,變成一個輕量級的鎖吧。

老大把鎖對象account恢復成無鎖狀態,在我們倆的棧幀中各自分配了一個空間,叫做Lock Record, 把鎖對象account的Mark Word在我們倆這裡各自複製了一份,叫做Displaced Mark Word, 這名字真奇怪。

然後把我的Lock Record的地址使用CAS放到了Mark Word當中,並且把鎖標誌位改為00, 這其實就意味著我也已經獲得了這個輕量級的鎖了,可以繼續進入臨界區執行。

JVM到底是怎麼處理鎖的?怎麼不讓我阻塞呢?

0x3704沒有獲得鎖,但還是不阻塞,老大讓他自旋幾次,等待一會兒。

等到我退出臨界區,釋放鎖的時候,需要把這個Displaced markd word 使用CAS複製回去。接下來他就可以加鎖了。

我們兩個交替著進入臨界區,執行這段代碼,相安無事,很少出現真正的競爭。

即使是出現了競爭,想獲得鎖的線程只要自旋幾次,等待一會兒,鎖就可能釋放了。

很明顯,如果沒有競爭或者輕度的競爭,輕量級鎖僅僅使用CAS操作和Lock record就避免了重量級互斥鎖的開銷,對JVM老大來說,確實是個好主意。

重量級鎖

輕量級鎖運行得挺好,我還是沒有機會休息,終於有這麼一天,0x3704 正在持有鎖,在臨界區辛苦地執行代碼。 我自旋了好多次,0x3704還是沒釋放鎖。 這時候JVM老大說: 自旋次數太多了,太浪費CPU了,接下來升級為重量級鎖!

這個重量級鎖需要操作系統的幫忙,依賴操作系統底層的Mutex Lock。

只見老大創建了一個monitor 對象, 把這個對象的地址更新到了Mark word當中。

鎖升級了!

JVM到底是怎麼處理鎖的?怎麼不讓我阻塞呢?

由於0x3704還在持有鎖運行,而我終於進入了夢寐以求的狀態:阻塞! 終於可以休息一下了!

仔細一想,老大煞費心機地設置了偏向鎖和輕量級鎖,就是為了避免阻塞,避免操作系統的介入, 這兩種鎖無非就是針對這兩種情況:

偏向鎖: 通常只有一個線程在臨界區執行

輕量級鎖: 可以有多個線程交替進入臨界區,在競爭不激烈的時候,稍微自旋等待一下就能獲得鎖。

至於重量級鎖,也是我最為期待的鎖,那就是出現了激烈的競爭,只好讓我們去阻塞休息了。

糾錯!jvm專家 你假笨指出一個錯誤 ,偏向鎖對象頭裡存的不是線程id ,其實存的是JavaThread對象的指針地址。圖片沒法修改了,在此更正。

為了讓學習變得輕鬆、高效,今天給大家免費分享一套阿里架構師傳授的一套教學資源。幫助大家在成為架構師的道路上披荊斬棘。

這套視頻課程詳細講解了(Spring,MyBatis,Netty源碼分析,高併發、高性能、分佈式、微服務架構的原理,JVM性能優化、分佈式架構)等這些成為架構師必備的內容!

而且還把框架需要用到的各種程序進行了打包,根據基礎視頻可以讓你輕鬆搭建分佈式框架環境,像在企業生產環境一樣進行學習和實踐。

JVM到底是怎麼處理鎖的?怎麼不讓我阻塞呢?

後臺私信回覆 “ 架構 ” 就可以馬上免費獲得這套價值一萬八的內部教材!


分享到:


相關文章: