JVM究竟是怎麼處理鎖的?怎麼不讓我阻塞呢?

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

我據說人類寫的代碼中有些特殊的地方,叫作臨界區,好比synchronized修飾的方法或者代碼塊,他們很是神奇,在同一時刻JVM老大隻容許一個線程進入執行。spring

實際上,老大設置了一把鎖,搶到了這把鎖就能夠執行,不然只能阻塞,等待別人釋放鎖。編程

老大說,阻塞就是不用幹活了,老老實實地等着就行。性能優化

居然還有這等美事! 趕忙讓我阻塞一次吧。微信

但是老大又說:「每次設置鎖我都得和操做系統打交道,請他在內核中維護一個什麼Mutex(互斥量)的東西,他還得把大家這些線程阻塞,切換,這但是一筆巨大的費用啊,因此這些鎖仍是少用爲妙。」數據結構

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

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

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

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

偏向鎖jvm

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

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

但是老大根本沒有去找操做系統, 只是看了看這個account對象的所謂「對象頭」,其中有個叫作Mark Word的東西,彷佛是個什麼數據結構, 裏邊有幾個標識位,還有其餘數據。

老大隨手使用CAS操做把個人線程ID記錄到了這個Mark Word當中,修改了標識位,而後告訴我說: 能夠了,你如今擁有這把鎖了,進去執行代碼吧。

我驚奇地說:「老大你不和操做系統協商設置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, 這其實就意味着我也已經得到了這個輕量級的鎖了,能夠繼續進入臨界區執行。

0x3704沒有得到鎖,但仍是不阻塞,老大讓他自旋幾回,等待一下子。

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

咱們兩個交替着進入臨界區,執行這段代碼,相安無事,不多出現真正的競爭。

即便是出現了競爭,想得到鎖的線程只要自旋幾回,等待一下子,鎖就可能釋放了。

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

重量級鎖

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

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

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

鎖升級了!

因爲0x3704還在持有鎖運行,而我終於進入了求之不得的狀態:阻塞!  終於能夠休息一下了!

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

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

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

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

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

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

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

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

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

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

但是老大又說:「每次設置鎖我都得和操做系統打交道,請他在內核中維護一個什麼Mutex(互斥量)的東西,他還得把大家這些線程阻塞,切換,這但是一筆巨大的費用啊,因此這些鎖仍是少用爲妙。」

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

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

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

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

偏向鎖

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

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

但是老大根本沒有去找操做系統, 只是看了看這個account對象的所謂「對象頭」,其中有個叫作Mark Word的東西,彷佛是個什麼數據結構, 裏邊有幾個標識位,還有其餘數據。

老大隨手使用CAS操做把個人線程ID記錄到了這個Mark Word當中,修改了標識位,而後告訴我說: 能夠了,你如今擁有這把鎖了,進去執行代碼吧。

我驚奇地說:「老大你不和操做系統協商設置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, 這其實就意味着我也已經得到了這個輕量級的鎖了,能夠繼續進入臨界區執行。

0x3704沒有得到鎖,但仍是不阻塞,老大讓他自旋幾回,等待一下子。

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

咱們兩個交替着進入臨界區,執行這段代碼,相安無事,不多出現真正的競爭。

即便是出現了競爭,想得到鎖的線程只要自旋幾回,等待一下子,鎖就可能釋放了。

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

重量級鎖

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

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

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

鎖升級了!

因爲0x3704還在持有鎖運行,而我終於進入了求之不得的狀態:阻塞!  終於能夠休息一下了!

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

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

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

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

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

原文出處:http://zhuanlan.51cto.com/art/201807/578802.htm

注:關注做者微信公衆號,瞭解更多分佈式架構、微服務、netty、MySQL、spring、、性能優化、等知識點。

公衆號:《 Java大蝸牛 

相關文章
相關標籤/搜索