1. 程式人生 > >輕量級鎖、偏向鎖、重量級鎖詳情

輕量級鎖、偏向鎖、重量級鎖詳情

這篇文章是上篇文章是否真的理解了偏向鎖、輕量級鎖、重量級鎖(鎖膨脹)、自旋鎖、鎖消除、鎖粗化,知道重偏向嗎?的補充,對於偏向鎖,網上有些對於它的原理解讀過於簡單,簡單得似乎是錯誤的,最顯眼的是對於Mark Word的倒數第三位的作用的含義,許多部落格對於這個的作用搞成標誌是否使用偏向鎖,其實還有層含義是是否禁用偏向。

 

這篇文章提出了對於一書“深入理解Java虛擬機器”中的一張圖的深入解讀,當然這張圖是Oracle官方的,呵呵。還必須具有深入知道偏向鎖、輕量級鎖、重量級鎖(鎖膨脹)、自旋鎖、鎖消除、鎖粗化這幾個的過程,如果不知道。前面那個傳送門可以去看一下,我是從作業系統層的PV操作的角度入手解讀輕量級鎖和偏向鎖的,當然JVM底層實現呼叫作業系統的api的確是用的那個,針對於Monitor,C沒有實現Monitor,Java卻實現了,提供給我們很好的Monitor實現工具。像synchronized和ReetrantLock,不過這兒宣告,synchronized單個不是Monitor,這兒給個我在一本書上看到Monitor(管程)的定義“管程由一些共享資料、一組能為併發程序所執行的作用在共享資料上的操作的集合、初始程式碼以及存取權組成。”,從這個上面解讀,Monitor還包括很多東西,還和業務資料有關。不過在網上看的很多對於Java中Monitor的解讀似乎很不符合這個原則,我也不知道什麼原因,可能是英語轉換成漢語讓人誤解的問題吧,這兒不詳細討論這個了。但是知道Monitor是用PV操作實現的。

先給出這張圖和一個Mark Word的結構:

鎖狀態 25bit 4bit 1bit 2bit
23bit 2bit 是否偏向鎖(是否禁用偏向) 鎖標誌位
無鎖態 物件的hashCode 分代年齡 0 01
輕量級鎖 指向棧中鎖記錄的指標 00
重量級鎖 指向互斥量(重量級鎖)的指標 10
GC標記 11
偏向鎖 執行緒ID Epoch 分代年齡 1 01

  

疑惑:

       1、重偏向是做什麼的?

        2、在已經初始鎖定的情況下 的偏向鎖為什麼還有鎖定和 解鎖?

        3、撤銷偏向,怎麼知道物件 有沒有鎖定?

當時為了解除這些疑惑我baidu到了另外的解釋:

HotSpot支援儲存釋放偏向鎖,以及偏向鎖的批量重偏向和撤銷。這個特性可以通過JVM的引數進行切換,而且這是預設支援的。Unlock狀態下MarkWord的一個位元位用於標識該物件偏向鎖是否被使用或者是否被禁止。如果該bit位為0,則該物件未被鎖定,並且禁止偏向;如果該bit位為1,則意味著該物件處於以下三種狀態:

  • 匿名偏向(Anonymously biased)
    在此狀態下thread_ptr為NULL(0),意味著還沒有執行緒偏向於這個鎖物件。第一個試圖獲取該鎖的執行緒將會面臨這個情況,使用原子CAS指令可將該鎖物件綁定於當前執行緒。這是允許偏向鎖的類物件的初始狀態。
  • 可重偏向(Rebiasable)
    在此狀態下,偏向鎖的epoch欄位是無效的(與鎖物件對應的klass的mark_prototype的epoch值不匹配)。下一個試圖獲取鎖物件的執行緒將會面臨這個情況,使用原子CAS指令可將該鎖物件綁定於當前執行緒。在批量重偏向的操作中,未被持有的鎖物件都被至於這個狀態,以便允許被快速重偏向。
  • 已偏向(Biased)
    這種狀態下,thread ptr非空,且epoch為有效值——意味著其他執行緒正在只有這個鎖物件。

基於偏向鎖物件需要使用hashcode欄位作為偏向執行緒id標識的事實,被hash的物件不可被用作偏向鎖。對於允許偏向的物件在進行hashcode計算時,首先要吊銷(revoke)所有的偏向(不管是有效的還是無效的),然後使用CAS將計算好的hashcode值放到MarkWord中,儘管這僅僅適用於“identity hashcode(使用Object類的hashcode()方法進行計算)”。普通Java型別hashcode的計算需要過載Object的hashcode()方法,但不必要去顯示呼叫這個方法;因此,對於沒有顯示呼叫Object#hashcode()方法的類的物件,仍然適用於偏向鎖的機制——可被用作鎖物件使用。

HotSpot為所有載入的型別,在class元資料——InstanceKlass中保留了一個MarkWord原型——mark_prototype。這個值的bias位域決定了該型別的物件是否允許被偏向鎖定。與此同時,當前的epoch位也被保留在prototype中。這意味著,對應class的新物件可以簡單地直接拷貝這個原型值,而不必在後面進行修正。在批量重偏向(bulk rebias)的操作中,prototype的epoch位將會被更新;在批量吊銷(bulk revoke)的操作中,prototype將會被置成不可偏向的狀態——bias位被置0。

偏向鎖的獲取依靠原子CAS指令將執行緒指標插入MarkWord中。其先決條件是:1.該物件處於匿名偏向狀態;2.該物件處於可重偏向狀態(一個鎖物件僅能被一個執行緒偏向一次)。只要鎖物件被偏向,遞迴鎖定和解鎖僅僅需要讀取物件頭以及對應Klass的prototype去驗證偏向是否被吊銷。

HotSpot中偏向鎖的撤銷是JVM處於在全域性安全點時被執行的。在撤銷過程中,撤銷者會遍歷當前偏向執行緒的鎖記錄,藉此推斷物件當前是否被鎖定。如果發現鎖物件被一個偏向執行緒持有,鎖記錄將被修改——如同輕量級鎖被使用一樣;如果鎖物件未被持有,這是取決於觸發撤銷的原因,鎖物件要麼被禁止用作偏向鎖,要麼被禁止重新偏向於撤銷執行緒。

即使偏向鎖的特性被開啟,出於效能(啟動時間)的原因在JVM啟動後的的頭4秒鐘這個feature是被禁止的。這也意味著在此期間,prototype MarkWord會將它們的bias位設定為0,以禁止例項化的物件被偏向。4秒鐘之後,所有的prototype MarkWord的bias位會被重設為1,如此新的物件就可以被偏向鎖定了。

獲取偏向鎖的步驟:

  1. 驗證物件的bias位
    如果是0,則該物件不可偏向,應該使用輕量級鎖演算法。
  2. 驗證物件所屬InstanceKlass的prototype的bias位
    確認prototype的bias為是否被設定。如果沒有設定,則該類所有物件全部不允許被偏向鎖定;並且該類所有物件的bias位都需要被重置,使用輕量級鎖替換。
  3. 校驗epoch位
    校驗物件的MarkWord的epoch位是否與該物件所屬InstanceKlass的prototype的MarkWord的epoch匹配。如果不匹配,則表明偏向已過期,需要重新偏向。這種情況,偏向執行緒可以簡單地使用原子CAS指令重新偏向於這個鎖物件。
  4. 校驗owner執行緒
    比較偏向執行緒ID與當前執行緒ID。如果匹配,則表明當前執行緒已經獲得了偏向,可以安全返回。如果不匹配,物件鎖被假定為匿名偏向狀態,當前執行緒應該嘗試使用CAS指令獲得偏向。如果失敗的話,就嘗試撤銷(很可能引入安全點),然後回退到輕量級鎖;如果成功,當前執行緒成功獲得偏向,可直接返回。

從上面,我對於

  • 匿名偏向(Anonymously biased)
    在此狀態下thread_ptr為NULL(0),意味著還沒有執行緒偏向於這個鎖物件。第一個試圖獲取該鎖的執行緒將會面臨這個情況,使用原子CAS指令可將該鎖物件綁定於當前執行緒。這是允許偏向鎖的類物件的初始狀態。
  • 可重偏向(Rebiasable)
    在此狀態下,偏向鎖的epoch欄位是無效的(與鎖物件對應的klass的mark_prototype的epoch值不匹配)。下一個試圖獲取鎖物件的執行緒將會面臨這個情況,使用原子CAS指令可將該鎖物件綁定於當前執行緒。在批量重偏向的操作中,未被持有的鎖物件都被至於這個狀態,以便允許被快速重偏向。
  • 已偏向(Biased)
    這種狀態下,thread ptr非空,且epoch為有效值——意味著其他執行緒正在只有這個鎖物件。

疑惑了,疑惑如下:

     4、可重偏向這個狀態是做什麼的,為什麼 Epoch的欄位無效,還有就是epoch欄位有什 麼作用?

對於這個過程:

  1. 驗證物件的bias位
    如果是0,則該物件不可偏向,應該使用輕量級鎖演算法。
  2. 驗證物件所屬InstanceKlass的prototype的bias位
    確認prototype的bias為是否被設定。如果沒有設定,則該類所有物件全部不允許被偏向鎖定;並且該類所有物件的bias位都需要被重置,使用輕量級鎖替換。
  3. 校驗epoch位
    校驗物件的MarkWord的epoch位是否與該物件所屬InstanceKlass的prototype的MarkWord的epoch匹配。如果不匹配,則表明偏向已過期,需要重新偏向。這種情況,偏向執行緒可以簡單地使用原子CAS指令重新偏向於這個鎖物件。
  4. 校驗owner執行緒
    比較偏向執行緒ID與當前執行緒ID。如果匹配,則表明當前執行緒已經獲得了偏向,可以安全返回。如果不匹配,物件鎖被假定為匿名偏向狀態,當前執行緒應該嘗試使用CAS指令獲得偏向。如果失敗的話,就嘗試撤銷(很可能引入安全點),然後回退到輕量級鎖;如果成功,當前執行緒成功獲得偏向,可直接返回。

疑惑,疑惑如下:

      5、為什麼校驗epoch位,如果 InstanceKlass的prototype的MarkWord的 Epoch和物件的MarkWord的epoch位不 匹配表示 偏向已過期,可以重新偏向?

      6、為什麼在校驗owner執行緒,比較偏向 執行緒ID與當前執行緒ID如果不匹配,還要 嘗試使用CAS指令獲取偏向,並且如果 成功了還表示偏向成功?

根據這兩個還是沒有從這些疑惑中找到相同點,尋找到合理的理解,公司可以翻牆,所以我google了下,查到一篇部落格:

    https://pdfs.semanticscholar.org/edf9/54412a9b1ce955bea148199f325759779540.pdf

從中知道了原來在偏向鎖“可重偏向”是為了“在一個時間段內每一個時刻都是隻有一個執行緒使用同一個物件,但是不是每一時刻都是同一個執行緒”而。

  • 到全域性安全點的時候才停止所有執行緒。。
  • 增加型別C的epoch的值 定位當前阻塞的執行緒鎖住了的型別C的例項物件並且修改他們的偏向epoch或者撤銷偏向。
  • 釋放在安全點阻塞的執行緒。

疑惑:

      7、這個裡面為什麼 有型別的epoch和 例項物件的epoch?

      8、為什麼要自增型別C的epoch的值?

      9、為什麼只更新當前阻塞的執行緒鎖住了的型別 C的例項物件的epoch?

最後詳細的品味,詳細的想終於將一切想通了。

總結疑惑:

1、重偏向是做什麼的?

2、在已經初始鎖定的情 況下的偏向鎖為什麼還 有鎖定和解鎖?

3、撤銷偏向,怎麼知道 物件有沒有鎖定?

4、可重偏向這個狀態是 做什麼的,為什麼Epoch 的欄位無效,還有就是 epoch欄位有什麼作用?

5、這個裡面為什麼 有型別的epoch和實 例物件的epoch?

6、為什麼要增加類 型C的epoch的值?

7、為什麼只更新當 前阻塞的執行緒鎖住了 的型別C的例項物件 的epoch?

8、為什麼校驗epoch位,如果 InstanceKlass的prototype的MarkWord的 Epoch和物件的MarkWord的epoch位不 匹配表示 偏向已過期,可以重新偏向。

9、為什麼在校驗owner執行緒,比較偏向 執行緒ID與當前執行緒ID如果不匹配,還要 嘗試使用CAS指令獲取偏向,並且如果 成功了還表示偏向成功?

解決:

1、重偏向不是針對“ 在一個時間段內每一個 時刻都是相同執行緒使用 同一個物件”的情景,而 是在不同時間不同執行緒 使用相同物件的情景, “重偏向” 是就是為了 更新執行緒ID。

2、在已經初始鎖定的情 況下的偏向鎖為什麼還 有鎖定和解鎖?

3、雖然物件中沒有標記 設定這個物件有沒有在 使用,但是可以從執行緒 物件Mark Word中執行緒ID 是否處於活動狀態隱式?

4、可重偏向這個狀態也 是針對“不同時間不同線 程使用相同物件的情景” 的情景,當物件中的epoch 和InstanceKlass中的epoch 值不相同的時候,說明這 個物件沒有其它執行緒使用 了,所以物件的epoch字 段無效,下一個執行緒可以 繼續使用,並且偏向。 Epoch其實就是為了標誌 物件是否被執行緒鎖住。

5、因為需要知道有 哪些型別C的例項物件 被阻塞的執行緒鎖住 了,因為滿足這個條 件的說明是活動的, 如果其他執行緒在這個 時候來發生競爭,那 麼就CAS失敗,進入 輕量級鎖。

6、因為為了讓上次 鎖住的物件但是這次 已經不再有執行緒偏向 區別開。

7、這個問題上面 已經回答了,因為 阻塞的就是正在發 生偏向的,偏向還 沒有失效。失效了 的可以重偏向。

8、校驗epoch位是為了知道該物件 是否處於活動狀態,如果確認和 InstanceKlass不相同,說明上次鎖住這 個物件偏向的執行緒已經不再存活,可以 重新偏向了。

9、因為校驗ower執行緒,比較當前執行緒 ID和物件裡面的ID如果不相等,還有 可能可以發生重偏向,它的場景是“ 在一個時間段內每一個時刻都是隻 有一個執行緒使用同一個物件,但是不 是每一時刻都是同一個執行緒”。

下面是我在網上找到的流程圖,這裡面完美總結了,只不過倒數3位不但是否偏向,而且表示是否禁用偏向:

下面附上偏向鎖升級為輕量級鎖的步湊 :

      1、到全域性安全點的時候才停止所有執行緒。

     2、偏移所有者的堆疊被遍歷,並且這個鎖記錄鎖關聯的物件將被 填充成如果這個物件發生輕量級鎖生成的值。

     3、物件的標記字被更新為指向堆疊上最古老的關聯鎖記錄 。

     4、釋放在安全點阻塞的執行緒。

偏向鎖沒有那麼簡單