1. 程式人生 > 其它 >JDK成長記21: ReentrantLock (4) 公平、非公平、可重入鎖是什麼?

JDK成長記21: ReentrantLock (4) 公平、非公平、可重入鎖是什麼?

經過前面的三節,相信你對ReentrantLock底層的AQS原理已經很清楚了。接下來給大家介紹幾個ReentrantLock中的幾個概念:

  • 公平,非公平鎖的概念
  • ReentrantLock是如何實現非公平和公平的?
  • 可重入鎖又是什麼東西?

公平鎖 Vs 非公平鎖

公平鎖 Vs 非公平鎖

當你掌握了ReentrantLock加鎖,加鎖失敗入隊,釋放鎖的原理後。其實在ReenrantLock中還需要搞明白幾個概念,比如獨佔鎖、共享鎖、可重入鎖,公平鎖和非公平鎖這些都是什麼意思。

這一小節,我們先來聊聊公平和非公平鎖。

什麼是公平鎖?什麼又是非公平鎖呢?這裡給大家舉個例子:

相信你肯定有過排隊的經歷,比如你給女朋友排隊買過奶茶。但是你排隊排的好好的,突然當有個老闆親戚或者關係戶過來插了一個隊,你是什麼感覺?是不是感覺不太公平。但是有的關係戶也很有修養,不會插隊,會老老實實去排隊,這就很公平了, 因為先來後到麼。

這其實就是公平和非公平的鎖的意思。你可以想想,還是上面的例子,執行緒2在排隊了,此時執行緒1釋放了鎖,可是突然來了一個執行緒3,也來加鎖,是不是可能線上程2出隊的過程中,執行緒3搶到鎖,這就是非公平的,執行緒3插隊了,沒有老老實實排隊。

但是如果執行緒3,老老實實的排隊,進入AQS的佇列中,這樣就是公平鎖。如下圖所示:

ReentrantLock是如何實現非公平和公平的?

ReentrantLock是如何實現非公平和公平的?

具體程式碼是怎麼做到呢?核心是通過兩個Sync的子類。FairSync和NonfairSync。從名字上看,你就應該知道,這兩個類是公平和非公平AQS的Sync元件意思。

大家可以它們兩個類的找找不同看看:

static final class NonfairSync extends Sync {
 private static final long serialVersionUID = 7316153563782823691L;


 final void lock() {
​    if (compareAndSetState(0, 1))
​      setExclusiveOwnerThread(Thread.currentThread());
​    else
​      acquire(1);
 }


 protected final boolean tryAcquire(int acquires) {
​    return nonfairTryAcquire(acquires);
 }
}


final boolean nonfairTryAcquire(int acquires) {
 final Thread current = Thread.currentThread();
 int c = getState();
 if (c == 0) {
​    if (compareAndSetState(0, acquires)) {
​      setExclusiveOwnerThread(current);
​      return true;
​    }
 }

 else if (current == getExclusiveOwnerThread()) {
​    int nextc = c + acquires;
​    if (nextc < 0) // overflow
​      throw new Error("Maximum lock count exceeded");
​    setState(nextc);
​    return true;
 }
 return false;

}
static final class FairSync extends Sync {
  private static final long serialVersionUID = -3000897897090466540L;

  final void lock() {
​    acquire(1);
  }

 

  protected final boolean tryAcquire(int acquires) {
​    final Thread current = Thread.currentThread();
​    int c = getState();
​    if (c == 0) {
​      if (!hasQueuedPredecessors() &&
​        compareAndSetState(0, acquires)) {
​        setExclusiveOwnerThread(current);
​        return true;
​      }
​    }

​    else if (current == getExclusiveOwnerThread()) {
​      int nextc = c + acquires;
​      if (nextc < 0)
​        throw new Error("Maximum lock count exceeded");
​      setState(nextc);
​      return true;
​    }
​    return false;

  }

}

首先是lock方法,區別就是在一個if判斷,非公平的鎖NonfairSync會多了一個判斷,先嚐試來加個鎖。

這個區別是什麼意思呢?你可以理解為如果執行緒1釋放了,別人過來加鎖,直接先嚐試插個隊的意思,有可能AQS佇列中的執行緒2還沒被喚醒了,被別人搶走了鎖,讓別的執行緒加鎖成功了。

如何把鎖給釋放掉,另外一個是如果鎖徹底釋放了以後,如何讓佇列中的隊頭的那個執行緒來喚醒嘗試獲取鎖。

而另一個方法,嘗試加鎖,唯一的區別是一個if條件

hasQueuedPredecessors()

這方法從名字就能看出來,判斷下佇列中有沒有有元素。程式碼如下:

 public final boolean hasQueuedPredecessors() {
  Node t = tail; // Read fields in reverse initialization order
  Node h = head;
  Node s;
  return h != t &&
   ((s = h.next) == null || s.thread != Thread.currentThread());
}

也就是說,在公平鎖的嘗試加鎖的程式碼中,有一個限制如果有人排隊,其他執行緒就不能插隊加鎖。所以就算執行緒1釋放鎖,執行緒3過來加鎖,由於lock方法沒有了非公平鎖的if(上來嘗試CAS修改state,加鎖的程式碼),執行緒3就只能入隊,如果執行緒3執行到嘗試獲取鎖的程式碼時,公平鎖比非公平鎖的程式碼多了一個判斷,判斷佇列中是否有等待執行緒。有的話也只能乖乖排隊。如下圖所示:

可重入鎖 Vs 不可重入鎖

可重入鎖 Vs 不可重入鎖

之前提到,ReentrantLock涉及了一些鎖的概念,講過了公平和非公平鎖的概念後,今天我們最後聊一下可重入鎖。

其實這個比較好理解,ReentrantLock通過AQS的state變數巧妙的實現了可重入加鎖。如果是同一個執行緒呼叫了lock方法,加鎖,state會在現有值上加+1,每再次加一次鎖,就是一次可重入,所以就加鎖可重入鎖。也就是說:

同一個執行緒可以使用同一個ReentrantLock進行反覆加鎖。

另外,釋放鎖的話,肯定需要釋放所多次,同一個執行緒加鎖了幾次,就需要釋放幾次,需要將state值恢復為0才算真正的釋放鎖,別的執行緒才能獲取到。

由於比較簡單,就不帶大家看原始碼實現了。你可以自己在原始碼中找找。核心還是掌握AQS加鎖釋放鎖的原理最重要。

獨佔鎖 VS 共享鎖

獨佔鎖 VS 共享鎖

至於獨佔和共享鎖的概念之後講解讀寫鎖的時候會提到。這裡先簡單的講一下。

所謂獨佔鎖,就是隻要有一個執行緒加鎖,其他人都得靠邊站,這把鎖屬於某個執行緒獨佔,這就是獨佔鎖。

預設reentrantLock.lock建立的鎖是什麼的呢?非公平的可重入獨佔鎖!

共享鎖是什麼意思呢?意思就是可以和別的執行緒同時持有一把鎖,比如之後要將的讀寫鎖。執行緒1加了讀鎖,執行緒2還是可以加讀鎖的,它們共享一把鎖。這樣的鎖就是一把共享鎖。

當然讀寫鎖之間是有一些互斥關係的,所以下一節我們就來探索下,如何使用讀寫鎖、讀寫鎖的原理具體是什麼、以及讀寫鎖的互斥關係。

小結&思考

小結&思考

其實這一節並沒有什麼特別負責複雜的知識,主要帶大家看了 ReentrantLock的重入實現

核心思想就是通過程式碼執行順序,CAS操作順序和一個if判斷佇列是否有等待執行緒實現的。

其次就是介紹了幾個概念,可重入鎖、公平、非公平鎖、獨佔和共享鎖分別是什麼意思。

ReentrantLock的原理其實到這裡我們大體就分析完成了,你起碼掌握了 ReentrantLock的基於抽象類封裝3個元件(變數)的操作設計。

之前我們提到的synchronized的底層ObjectMonitor,其實是不是也是用過這個思想設計的,只不過一個是C++封裝的ObjectMonitor物件,一個是Java封裝的ReentrantLock物件。

大家當學完一個技術的原理和或者原始碼,或者做完一個專案後,一定要學會進行思考,思考之後才能更好的應用這個技術、更好的解決問題。

另外有興趣的同學可以去深究下,它使用的CAS底層JVM C++語言如何的實現,為什麼用LockSupport.park掛起執行緒,LockSupport的park方法實現等等。。

下一節我們開始研究下ReentrantReadWriteLock的實現原理,我們下一節見!

本文由部落格群發一文多發等運營工具平臺 OpenWrite 釋出