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 釋出