前言
jdk中鎖的底層實作是AQS,上一篇文章有講解它的源碼
這篇文章跟大家分享下讀寫鎖,公平鎖,非公平鎖的實作原理。首先他們都是基于AQS實作的。
AQS提供了tryAcquire,tryRelease的方法來供它的子類來實作,不同的鎖通過不同的實作,進而産生了不同的鎖效果。這在設計模式中屬于模闆方法(核心的代碼流程都是一樣的先定義好,不同的類的實作方法表現出不同的效果)。
下面看下他們都是怎麼實作的
非公平鎖實作
首先入口ReentrantLock,它的使用方法這裡不做說明了,很簡單,可以自行網上搜尋。
它的構造方法如下:
ReentrantLock
public ReentrantLock() {
// 無參構造預設是非公平鎖
sync = new NonfairSync();
}
/**
* Creates an instance of {@code ReentrantLock} with the
* given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantLock(boolean fair) {
// 可以在構造方法中指定參數來建立公平鎖還是非公平鎖
sync = fair ? new FairSync() : new NonfairSync();
}
先分析非公平鎖的實作
NonfairSync
/**
* Sync object for non-fair locks
*/
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
// 原子操作,如果state目前為0,則更新成1。NonfairSync繼承了Sync,Sync繼承AQS,state
// 是AQS中的一個屬性。如果多個線程同時調用compareAndSetState方法,同一時間肯定隻有一個
// 成功傳回true,其他的就是進入acquire方法
if (compareAndSetState(0, 1))
// 如果狀态設定成功,則設定目前線程為獨占線程
setExclusiveOwnerThread(Thread.currentThread());
else
// 該方法會先調用tryAcquire方法,如果成功則可以直接執行你的代碼塊,如果失敗則
// 建立一個Node節點,并且把目前線程作為一個屬性添加到該Node節點上,然後入AQS中的隊列
//,最後再阻塞目前線程運作。
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
上面的注釋寫的很清楚了,如果調用了acquire方法,那麼就會先執行tryAcquire(1)方法,如果該方法傳回true,則表示lock成功了,可以執行你的代碼。如果傳回false,則會入AQS隊列,并阻塞目前線程運作,這塊是标準AQS流程,前面文章已經說過了。這裡隻分析tryAcquire(1)方法。
上面的代碼中可以看到tryAcquire調用了nonfairTryAcquire方法,該方法是它的父類Sync中實作的,如下:
Sync
final boolean nonfairTryAcquire(int acquires) {
// 擷取目前線程
final Thread current = Thread.currentThread();
// 擷取AQS中的state屬性
int c = getState();
// 判斷該屬性是否為0,如果為0,則進入争搶鎖分支
if (c == 0) {
// 通過compareAndSetState方法實作鎖的争搶,因為多個線程執行該方法,同一時間隻有一個會成功。
// 這個方法是原子的操作。
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 如果state不等于0,證明鎖已經被加上了,然後再看加鎖的線程和目前線程是不是同一個線程
else if (current == getExclusiveOwnerThread()) {
// 如果是同一個線程,則把state屬性的值加1,然後再設定給state屬性。
// 這裡也展現出了可重入鎖的特性了,同一個線程内可以多次加鎖
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
// 給state屬性設定新值。
setState(nextc);
return true;
}
// 如果不是上面兩種情況則傳回false,然後就是進入AQS隊列,并阻塞目前線程
return false;
}
上面是非公平鎖的加鎖流程,主要流程很簡單,就是調用compareAndSetState方法争搶鎖,搶成功則執行代碼塊,搶失敗,入AQS隊列然後阻塞目前線程運作。
下面分析解鎖代碼
ReentrantLock調用unlock方法如下:
ReentrantLock
public void unlock() {
sync.release(1);
}
release是AQS的是否鎖方法,它會先調用tryRelease方法,如果該方法傳回true則會周遊AQS隊列,找到下一個節點線程來喚醒,進而執行該線程的代碼。如果tryRelease傳回false那麼解鎖失敗,不會喚醒下一個節點線程。這個也是AQS的核心流程。這裡主要分析tryRelease部分代碼。
NonfairSync沒有定義tryRelease方法,是以會調用它的父類Sync中定義的tryRelease方法,如下:
Sync
protected final boolean tryRelease(int releases) {
// 這裡releases是1,這裡先擷取state的值,然後減1.
int c = getState() - releases;
// 這裡主要判斷,調用release的線程一定要跟加鎖的線程是同一個線程,否則抛異常。
// 這個是合理的,因為加鎖和解鎖是成對出現的,目前線程加鎖了,解鎖的時候也一定是目前線程
// 調用tryRelease方法。
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// 如果c的值是0,那麼辨別解鎖成功,并請求加鎖線程的值。這裡c有可能不為0的,因為同一個線程
// 可能會加鎖多次,加鎖一次這個state值就會加1。是以隻有state值減到0的時候,tryRelease才會
// 傳回true,辨別解鎖成功的。
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
總結
當ReentrantLock調用lock方法的時候,會調用NonfairSync的tryAcquire方法,該方法會先調用compareAndSetState進行鎖的争搶,争搶成功state值從0變成1,如果同一個線程再次加鎖那麼state會繼續加1變成2。
當ReentrantLock調用unlock方法的時候,會調用NonfairSync父類Sync的tryRelease方法,該方法會把AQS的state值減1,每調用一次unlock都會減去1。如果同一個線程調用了兩次lock那麼state是2,然後對應的會調用兩次unlock,會減去1兩次,最後會減成0。如果state是0,則tryRelease方法傳回true,然後就會執行AQS後續流程,擷取下一個節點線程去運作。
它是怎麼展現出非公平性的?
主要是在NonfairSync的tryAcquire方法中。該方法會調用compareAndSetState來争搶鎖,比如第一個線程調用lock方法,compareAndSetState傳回true加鎖成功了。然後後續再進來的線程,如果上一個加鎖線程沒有unlock,那進來的線程都會進入AQS隊列排隊。如果加鎖線程調用unlock了。
這時可能會發生鎖的争搶,那兩個線程呢?
即unlock觸發AQS喚醒下一個節點的線程和調用ReentrantLock的lock方法新線程。重新調用lock方法肯定會調用compareAndSetState方法,上一章講AQS中喚醒流程的時候也講解過,喚醒一個節點後,會再次調用tryAcquire方法。是以他們會調用compareAndSetState方法進行鎖的争搶,誰搶成功誰有機會執行,其實這是不公平的,新線程是後來的,AQS中排隊的線程是先到的,按順序新來的線程應該排隊執行的。這就是展現了非公平性。
有了非公平鎖的講解,公平鎖就容易了,下面分析公平鎖。
公平鎖實作
公平鎖是在ReentrantLock中定義FairSync。這裡還是主要分析tryAcquire和tryRelease操作。
加鎖代碼如下:
FairSync
final void lock() {
// 加鎖流程,調用AQS的核心流程,在acquire會調用tryAcquire方法來實作公平鎖的操作
acquire(1);
}
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
protected final boolean tryAcquire(int acquires) {
// 擷取目前線程
final Thread current = Thread.currentThread();
// 擷取目前AQS的state值
int c = getState();
// 如果state值為0,說明還沒有其他線程加鎖
if (c == 0) {
// hasQueuedPredecessors方法是檢查隊列中在目前線程前面是不是有其他的節點。如果
// 隊列前面有其他節點排隊,并且加鎖線程不是目前線程則該方法傳回false,然後AQS就會
// 把目前線程入隊列,然後阻塞。
// 如果隊列中沒有其他節點排隊,那麼就會争搶鎖,争搶成功設定目前線程會加鎖線程,然後傳回true
// 該線程代碼得以執行。
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
// 上面非公平鎖也講過,如果同一個線程多次加鎖,則這裡state值會加1。同時該方法傳回true
// 目前線程代碼會執行。同一個線程多次加鎖是不會阻塞的,這就是可重入鎖
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
// 更新state值。
setState(nextc);
return true;
}
// 否則傳回false
return false;
}
對于公平鎖的解鎖方法和非公平鎖是一樣,都是state值減去1,然後判斷是不是減到0了,如果為0,則喚醒AQS隊列中下一個節點線程執行。
總結
公平鎖tryAcquire和非公平鎖很像,主要就是多了一個hasQueuedPredecessors方法判斷,這也是公平鎖核心所在。公平鎖加鎖的時候會判斷下AQS目前隊列中是否有其他節點,如果有,那麼tryAcquire傳回false,然後目前線程節點會入AQS隊列排隊,等待unlock喚醒執行。
這裡就展現了公平鎖的公平性,新來的線程調用lock的時候,如果隊列中之前有其他線程排隊,那麼它是不會加鎖成功的,它也要排隊等待執行。
下一個文章給大家講解讀寫鎖的實作原理