Java高併發25-獨佔鎖ReentranLock的原理

一、類圖結構

25.1
25.1
  • ReentrantLock是一個可重入鎖,只有一個線程可以獲取到該鎖,其他線程想要獲取該鎖的時候會被放到AQS隊列中。
  • 從類圖中可以看到實現了Lock接口,內含一個Sync類型變量,該類型是繼承自AQS抽象類,同時又有兩個繼承了類,分別爲公平鎖和非公平鎖。
 Sync sync;
 
 public ReentrantLock() {
  sync = new NonfairLock();
 }
 
 public ReentrantLock(boolean fair) {
  sync = fair? new FairLock():new NonfairLock();
 }
  • 這裏的AQS中state變量代表可重入的次數,0爲該鎖爲空閒階段,1爲該鎖被某線程佔用,當該線程再次重入的時候,該值就會遞增;釋放的時候,該值就會遞減,直到遞減爲0,才表示該鎖已完全釋放,其他線程纔有拿到的機會。

二、獲取鎖

  • 從類中可以看到獲取鎖的方法是lock(),下面是實現
 public void lock() {
  sync.lock();
 }
  • 直接把鎖交給了Sync的實現類,這就取決於使用構造方法創建的是公平鎖還是非公平鎖。
  • 下面看一下非公平鎖lock的實現鎖
 public void lock() {
  // CAS設置狀態值
  if(compareAndSetState(0,1)) {
   setExclusiveOwnerThread(Thread.currentThread())
  }else {
   acquire(1);
  }
 }
  • 使用CAS算法,修改state的值爲1,同時調用setExclusiveOwnerThread方法來設置當前線程爲佔用的線程。如果被佔用了,那就調用acquire方法,傳遞參數爲1,下面看一下這個方法的源碼。
 public final void acquire(int arg) {
  // 調用ReentrantLock重寫的tryAcquire方法
  if(!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE) ,arg)) {
   selfInterrupt();
  }
 }
  • 之前說過AQS並沒有提供可用的tryAcquire方法,tryAcquire方法需要子類自定定製化,所以上述的代碼會調用ReentrantLock重寫的tryAquire方法。我們先看一下非公平鎖的代碼
 protected final boolean tryAcuqire(int acquires) {
  return nonfairTryAcquire(acquires);
 }
 
 final boolean nonfairTryAcquire(int acquires) {
  final Thread current = Thread.currentThread();
  int c = getState();
  
  if(c == 0) {
   // 當前AQS狀態值爲0,也就是沒有線程獲取到了該鎖,那麼我們讓當前線程佔用該鎖
   if(compareAndSetState(0,acquires)) {
    setExlusiveOwnerThread(current);
    return true;
   }else if(current == getExlusiveThread()){
    // 該鎖已經被當前線程佔用,那麼也就是重入的情況
    int nextc = c + acquires;
    if(nextc <0) { // 這種情況就是重入的次數太多了,超過了int正值的範疇
     throw new Error("Maxium lock count exceeded");
    }
    setState(nextc);
    return true;
   }else {
    return false;
   }
  }
 }
}
  • 代碼(4)會查看當前鎖的狀態值是否爲0,爲0則說明當前該鎖空閒,那麼就嘗試CAS獲取該鎖,將AQS的狀態值從0設置爲1,並設置當前鎖的持有者爲當前線程,然後返回true,如果當前狀態值不爲0則說明該鎖已經被某個線程所擁有,,所以代碼(5)查看當前線程是否爲該擁有者,如果當前線程是該鎖的持有者,則狀態值爲1,然後返回true,這裏需要注意的是,nextc<0說明可重入次數溢出了,如果當前該線程不是鎖的持有者則返回false,然後其會放入到AQS阻塞隊列。
  • 上面是非公平鎖的實現代碼,回過頭來看看非公平鎖體現在哪裏。首先非公平是說嘗試獲取鎖的線程並不一定比後嘗試獲取鎖的線程優先獲取鎖,
  • 這裏假設線程A調用了lock()方法執行到nonfairTryAcquire的代碼(4),發現當前狀態值不爲0,所以執行代碼(5),發現當前線程不是線程持有者,則執行代碼(6)返回false,然後當前線程放入AQS阻塞隊列。
  • 這時候線程B也調用了lock方法執行到nonfairTryAcquire的代碼(4),發現當前狀態值爲0了(假設佔有該鎖的其他下稱釋放了該鎖),所以通過CAS設置獲取了該鎖,明明是線程A先請求獲取該鎖,這就是非公平鎖的體現,這裏線程B在獲取鎖之前並沒有查看當前AQS隊列裏面是否有比自己更早請求該鎖的線程,而是使用了搶奪策略,那麼下面看看公平鎖是怎麼實現公平的,公平鎖的話只需要看FairSync重寫的tryAcquire方法。

 protected final boolean tryAcuqire(int acquires) {
  final Thread current = Thread.currentThread();
  int c = getState();
  // (7)當前AQS狀態值爲0
  if(c == 0) {
   // (8)公平性策略
   if(!hasQueuedPredecessors() && compareAndSetState(0,acquires)) {
    setExclusiveOwnerThread(current);
    return true;
   }
  }
  
  // (9)當前線程是該鎖持有者
  else if(current == getExclusiveOwnerThread()) {
   int nextc = c + acquires;
   if(nextc<0) {
    throw new Error("Maximum lock count exceeded");
   }
   setState(nextc);
   return true;
  }
  return false;
 }

三、源碼:

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