Java高併發13-公平鎖與非公平鎖、自旋鎖、可重入鎖

一、複習

  • 僞共享原理以及如何避免
  • 樂觀鎖和悲觀鎖

二、公平鎖與非公平鎖

  • 按照線程請求並獲得鎖的時間順序,可以將鎖分爲公平鎖和非公平鎖
  • 公平鎖:線程獲取鎖的順序是按照線程請求鎖的時間早晚來進行劃分的,也就是滿足先到先得的原則;
  • 非公平鎖:線程在運行時闖入的,並不是按照先到先得的原則。

1.Java中兩種鎖的實現機制

  • Reentrant reentrant = new Reentrant(true)代表公平鎖
  • Reentrant reentrant = new Reentrant(false)代表非公平鎖
  • 如果構造函數不傳入參數的話,那麼默認就是非公平鎖
  • 在沒有公平性需求的前提下儘量使用非公平鎖,因爲公平鎖會帶來額外的開銷。

三、獨佔鎖和共享鎖

  • 按照一個資源是否可以同時被多個線程持有,或者只能被一個線程持有,可以分爲獨佔鎖和共享鎖。ReetrantLock鎖是一個獨佔鎖,同一時間只能由一個線程所持有;共享鎖可以由多個線程共同 持有,比如:ReadWriteLock.
  • 獨佔鎖是一中悲觀鎖,這種必須先加排他鎖才能對資源進行訪問,限制了併發性;共享鎖是一中樂觀鎖,這種放寬了加鎖的條件,允許多個線程能夠同時訪問資源。

四、可重入鎖

  • 定義:當一個線程要獲取一個被其他線程佔有的獨佔鎖時,該線程會被阻塞,那麼當一個線程獲取它自己已經獲取的鎖時是否會被阻塞起來呢?如果不會阻塞就可以稱爲可重入鎖
  • 下面舉一個例子
package com.ruigege.PricipleAnalyzingOfThreadLocalRandom3;

public class Hello {
 public static void main(String[] args) {
  new Hello().helloB();
 }
 
 public synchronized void helloA() {
  System.out.println("HelloA");
 }
 
 public synchronized void helloB() {
  System.out.println("HelloB");
  helloA();
 }
}
13.1
13.1
  • 代碼解析:hellB方法調用,會先獲取得內置鎖,然後打印輸出,之後調用helloA方法,在調用之前會先獲取內置鎖,如果內置鎖不是可重入的,那麼調用線程將會一直阻塞。
  • 實際上synchronized內部鎖是一個可重入鎖,可重入鎖的原理是在鎖的內部維護一個線程標示,用於標示該鎖目前正在被哪個線程佔用,然後關聯一個計數器,一開始計數器值爲0,說明該鎖沒有被任何線程佔用,當一個線程獲取了該鎖時,計數器的值會變成1,計數器的值會變成1,這時其他線程再來獲取該鎖時會發現鎖的所有者不是自己而被阻塞掛起。但是當獲取了該鎖的線程再次獲得鎖的時候發現鎖的擁有者時自己,就會把計算器值+1,當釋放鎖後計數器-1,當計數器爲0的時候,鎖裏面的線程標示被重置爲null,這時候被阻塞的線程會被喚醒來競爭獲取該鎖。

五、自旋鎖

  • 由於Java中的線程是與操作系統中的線程一一對應的,所以當一個線程在獲取鎖(比如獨佔鎖)失敗後,會被切換到內核狀態而被掛起,當該線程獲取到鎖時又需要將其切換到內核狀態而喚醒該線程。而從用戶狀態切換到內核狀態的開銷是比較大的,在一定程度上會影響併發性能,自旋鎖則是,當前線程在獲取鎖的時候,如果發現這個鎖已經被其他的鎖佔用了,他不能馬上阻塞自己,在不放棄CPU使用權的情況下,多次嘗試獲取(默認次數10,可以使用-XX:PreBlockSpinsh參數設置該值),很有可能在後面幾次嘗試中其他線程已經釋放了鎖,如果嘗試指定的次數後仍沒有獲取到鎖則當前線程纔會被阻塞掛起,由此看來自旋鎖時使用了CPU時間獲取線程阻塞與調度的開銷,但是很有可能這些CPU時間白白浪費了。

六、源碼:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章