中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

java中鎖機制的示例分析

發布時間:2022-01-14 14:19:57 來源:億速云 閱讀:120 作者:小新 欄目:大數據

這篇文章主要介紹java中鎖機制的示例分析,文中介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們一定要看完!

何為同步?JVM規范規定JVM基于進入和退出Monitor對象來實現方法同步和代碼塊同步,但兩者的實現細節不一樣。代碼塊同步是使用monitorenter和monitorexit指令實現,而方法同步是使用另外一種方式實現的,細節在JVM規范里并沒有詳細說明,但是方法的同步同樣可以使用這兩個指令來實現。monitorenter指令是在編譯后插入到同步代碼塊的開始位置,而monitorexit是插入到方法結束處和異常處, JVM要保證每個monitorenter必須有對應的monitorexit與之配對。任何對象都有一個 monitor 與之關聯,當且一個monitor 被持有后,它將處于鎖定狀態。線程執行到 monitorenter 指令時,將會嘗試獲取對象所對應的 monitor 的所有權,即嘗試獲得對象的鎖。

java中鎖機制的示例分析java中鎖機制的示例分析

Java對象頭

鎖存在Java對象頭里。如果對象是數組類型,則虛擬機用3個Word(字寬)存儲對象頭,如果對象是非數組類型,則用2字寬存儲對象頭。在32位虛擬機中,一字寬等于四字節,即32bit。

java中鎖機制的示例分析

Java對象頭里的Mark Word里默認存儲對象的HashCode,分代年齡和鎖標記位。32位JVM的Mark Word的默認存儲結構如下:

java中鎖機制的示例分析

java中鎖機制的示例分析在運行期間Mark Word里存儲的數據會隨著鎖標志位的變化而變化。Mark Word可能變化為存儲以下4種數據:

幾種鎖的類型

線程的阻塞和喚醒需要CPU從用戶態轉為核心態,頻繁的阻塞和喚醒對CPU來說是一件負擔很重的工作。

Java SE1.6為了減少獲得鎖和釋放鎖所帶來的性能消耗,引入了“偏向鎖”和“輕量級鎖”,所以在Java SE1.6里鎖一共有四種狀態,無鎖狀態,偏向鎖狀態,輕量級鎖狀態和重量級鎖狀態,它會隨著競爭情況逐漸升級。鎖可以升級但不能降級,意味著偏向鎖升級成輕量級鎖后不能降級成偏向鎖。這種鎖升級卻不能降級的策略,目的是為了提高獲得鎖和釋放鎖的效率。

偏向鎖

大多數情況下鎖不僅不存在多線程競爭,而且總是由同一線程多次獲得。偏向鎖的目的是在某個線程獲得鎖之后,消除這個線程鎖重入(CAS)的開銷,看起來讓這個線程得到了偏護。另外,JVM對那種會有多線程加鎖,但不存在鎖競爭的情況也做了優化,聽起來比較拗口,但在現實應用中確實是可能出現這種情況,因為線程之前除了互斥之外也可能發生同步關系,被同步的兩個線程(一前一后)對共享對象鎖的競爭很可能是沒有沖突的。對這種情況,JVM用一個epoch表示一個偏向鎖的時間戳(真實地生成一個時間戳代價還是蠻大的,因此這里應當理解為一種類似時間戳的identifier)

偏向鎖的獲取

當一個線程訪問同步塊并獲取鎖時,會在對象頭和棧幀中的鎖記錄里存儲鎖偏向的線程ID,以后該線程在進入和退出同步塊時不需要花費CAS操作來加鎖和解鎖,而只需簡單的測試一下對象頭的Mark Word里是否存儲著指向當前線程的偏向鎖,如果測試成功,表示線程已經獲得了鎖,如果測試失敗,則需要再測試下Mark Word中偏向鎖的標識是否設置成1(表示當前是偏向鎖),如果沒有設置,則使用CAS競爭鎖,如果設置了,則嘗試使用CAS將對象頭的偏向鎖指向當前線程。

偏向鎖的撤銷

偏向鎖使用了一種等到競爭出現才釋放鎖的機制,所以當其他線程嘗試競爭偏向鎖時,持有偏向鎖的線程才會釋放鎖。偏向鎖的撤銷,需要等待全局安全點(在這個時間點上沒有字節碼正在執行),它會首先暫停擁有偏向鎖的線程,然后檢查持有偏向鎖的線程是否活著,如果線程不處于活動狀態,則將對象頭設置成無鎖狀態,如果線程仍然活著,擁有偏向鎖的棧會被執行,遍歷偏向對象的鎖記錄,棧中的鎖記錄和對象頭的Mark Word,要么重新偏向于其他線程,要么恢復到無鎖或者標記對象不適合作為偏向鎖,最后喚醒暫停的線程。

偏向鎖的設置

關閉偏向鎖:偏向鎖在Java 6和Java 7里是默認啟用的,但是它在應用程序啟動幾秒鐘之后才激活,如有必要可以使用JVM參數來關閉延遲-XX:BiasedLockingStartupDelay = 0。如果你確定自己應用程序里所有的鎖通常情況下處于競爭狀態,可以通過JVM參數關閉偏向鎖-XX:-UseBiasedLocking=false,那么默認會進入輕量級鎖狀態。

自旋鎖

線程的阻塞和喚醒需要CPU從用戶態轉為核心態,頻繁的阻塞和喚醒對CPU來說是一件負擔很重的工作。同時我們可以發現,很多對象鎖的鎖定狀態只會持續很短的一段時間,例如整數的自加操作,在很短的時間內阻塞并喚醒線程顯然不值得,為此引入了自旋鎖。

所謂“自旋”,就是讓線程去執行一個無意義的循環,循環結束后再去重新競爭鎖,如果競爭不到繼續循環,循環過程中線程會一直處于running狀態,但是基于JVM的線程調度,會出讓時間片,所以其他線程依舊有申請鎖和釋放鎖的機會。

自旋鎖省去了阻塞鎖的時間空間(隊列的維護等)開銷,但是長時間自旋就變成了“忙式等待”,忙式等待顯然還不如阻塞鎖。所以自旋的次數一般控制在一個范圍內,例如10,100等,在超出這個范圍后,自旋鎖會升級為阻塞鎖。

輕量級鎖

加鎖

線程在執行同步塊之前,JVM會先在當前線程的棧楨中創建用于存儲鎖記錄的空間,并將對象頭中的Mark Word復制到鎖記錄中,官方稱為Displaced Mark Word。然后線程嘗試使用CAS將對象頭中的Mark Word替換為指向鎖記錄的指針。如果成功,當前線程獲得鎖,如果失敗,則自旋獲取鎖,當自旋獲取鎖仍然失敗時,表示存在其他線程競爭鎖(兩條或兩條以上的線程競爭同一個鎖),則輕量級鎖會膨脹成重量級鎖。

解鎖

輕量級解鎖時,會使用原子的CAS操作來將Displaced Mark Word替換回到對象頭,如果成功,則表示同步過程已完成。如果失敗,表示有其他線程嘗試過獲取該鎖,則要在釋放鎖的同時喚醒被掛起的線程。

重量級鎖

重量鎖在JVM中又叫對象監視器(Monitor),它很像C中的Mutex,除了具備Mutex(0|1)互斥的功能,它還負責實現了Semaphore(信號量)的功能,也就是說它至少包含一個競爭鎖的隊列,和一個信號阻塞隊列(wait隊列),前者負責做互斥,后一個用于做線程同步。

鎖的優缺點對比

java中鎖機制的示例分析

可重入鎖

本文里面講的是廣義上的可重入鎖,而不是單指JAVA下的ReentrantLock。

可重入鎖,也叫做遞歸鎖,指的是同一線程 外層函數獲得鎖之后 ,內層遞歸函數仍然有獲取該鎖的代碼,但不受影響。
在JAVA環境下 ReentrantLock 和synchronized 都是 可重入鎖。

public class Test implements Runnable{
 public synchronized void get(){
   System.out.println(Thread.currentThread().getId());
   set();
 }
 public synchronized void set(){
   System.out.println(Thread.currentThread().getId());
 }
 @Override
 public void run() {
   get();
 }
 public static void main(String[] args) {
   Test ss=new Test();
   new Thread(ss).start();
   new Thread(ss).start();
   new Thread(ss).start();
 }
}

結果如下,是正確的,即同一個線程id被連續輸出兩次。

Threadid: 8

Threadid: 8

Threadid: 10

Threadid: 10

Threadid: 9

Threadid: 9

可重入鎖最大的作用是避免死鎖。

我們以自旋鎖作為例子。(注:自旋鎖,就是拿不到鎖的情況會不停自旋循環檢測來等待,不進入內核態沉睡,而是在用戶態自旋嘗試)

public class SpinLock {
 private AtomicReference<Thread> owner =new AtomicReference<>();
 public void lock(){
   Thread current = Thread.currentThread();
   while(!owner.compareAndSet(null, current)){
   }
 }

 public void unlock (){
   Thread current = Thread.currentThread();
   owner.compareAndSet(current, null);
 }
}

上面是自旋鎖的一種實現。

對于自旋鎖來說:

1、若有同一線程兩調用lock() ,會導致第二次調用lock位置進行自旋,產生了死鎖
說明這個鎖并不是可重入的。(在lock函數內,應驗證線程是否為已經獲得鎖的線程)
2、若1問題已經解決,當unlock()第一次調用時,就已經將鎖釋放了。實際上不應釋放鎖。
(采用計數次進行統計)

修改之后,如下:

public class SpinLock1 {
 private AtomicReference<Thread> owner =new AtomicReference<>();
 private int count =0;
 public void lock(){
   Thread current = Thread.currentThread();
   if(current==owner.get()) {
     count++;
     return ;
   }
   while(!owner.compareAndSet(null, current)){
   }
 }

 public void unlock (){
   Thread current = Thread.currentThread();
   if(current==owner.get()){
     if(count!=0){
       count--;
     }else{
       owner.compareAndSet(current, null);
     }
   }
 }
}

這種方式實現的自旋鎖即為可重入鎖。

另,看一下mutex的情況:

Mutex可以分為遞歸鎖(recursive mutex)和非遞歸鎖(non-recursive mutex)。可遞歸鎖也可稱為可重入鎖(reentrant mutex),
非遞歸鎖又叫不可重入鎖(non-reentrant mutex)。

二者唯一的區別是,同一個線程可以多次獲取同一個遞歸鎖,不會產生死鎖。而如果一個線程多次獲取同一個非遞歸鎖,則會產生死鎖。

Windows下的Mutex和Critical Section是可遞歸的。

Linux下的pthread_mutex_t鎖默認是非遞歸的。可以顯示的設置PTHREAD_MUTEX_RECURSIVE屬性,將pthread_mutex_t設為遞歸鎖。

公平鎖

公平和非公平鎖的隊列都基于鎖內部維護的一個雙向鏈表,表結點Node的值就是每一個請求當前鎖的線程。公平鎖則在于每次都是依次從隊首取值。

鎖的實現方式是基于如下幾點: 

表結點Node和狀態state的volatile關鍵字。

sum.misc.Unsafe.compareAndSet的原子操作(見附錄)。

非公平鎖

在等待鎖的過程中, 如果有任意新的線程妄圖獲取鎖,都是有很大的幾率直接獲取到鎖的。

以上是“java中鎖機制的示例分析”這篇文章的所有內容,感謝各位的閱讀!希望分享的內容對大家有幫助,更多相關知識,歡迎關注億速云行業資訊頻道!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

永平县| 精河县| 民权县| 轮台县| 尼玛县| 南乐县| 阿克| 长治市| 洛隆县| 彰武县| 平山县| 远安县| 黔西县| 平顺县| 科技| 昆山市| 休宁县| 兰考县| 枝江市| 龙口市| 永丰县| 平阳县| 安平县| 湟中县| 綦江县| 长宁县| 宿松县| 怀安县| 吉隆县| 五河县| 景东| 辰溪县| 海盐县| 商南县| 临清市| 锡林郭勒盟| 仙居县| 安多县| 通渭县| 彭水| 灵石县|