目錄
-
- Lock及ReentrantLock提供的功能
- Lock接口提供的方法
- ReentrantLock的實作
在并發多線程之AQS源碼分析(上)和并發多線程之AQS源碼分析(下)已經對JUC中的AQS源碼進行了剖析梳理,AQS可以說是juc包下所有工具的基礎架構,其提供了兩種隊列:同步隊列和條件隊列。并且控制了資源的擷取和釋放,以及線程的阻塞和喚醒排程。那麼接下來就是學習一下juc包下各個工具是怎麼基于AQS來實作的,這裡先從Lock及其實作類ReentrantLock開始。關于Lock及ReentrantLock的使用再次就不再贅述。
Lock及ReentrantLock提供的功能
ReentrantLock實作了Lock接口,并提供一下特性:
- 提供了獨占鎖的實作(類似于sychronized關鍵字的功能)
- 可以指定公平/非公平擷取鎖。
- 可重入。
- 允許中斷。
簡單說明一下各個特性。
獨占鎖:
ReentrantLock提供的是獨占鎖,也就是在同一時刻隻有一個線程可以擷取到鎖執行。
公平/非公平擷取鎖:
當指定公平的擷取鎖時,那麼在多個線程擷取鎖時,當有一個線程擷取到鎖後則進入代碼塊執行,而沒有擷取到鎖時則進入同步隊列等待被喚醒,而此時又有一個線程來回去鎖時,首先是判斷目前同步隊列中是否具有等待的線程,當有等待的線程,則新來的線程直接加入同步隊列中等待被喚醒。
而指定非公平擷取鎖時,新加入的線程就沒有那麼有“素質了”,其不會判斷目前同步隊列中是否有等待的線程,而是直接嘗試擷取鎖,如果擷取到則執行代碼塊,沒有擷取到則進入同步隊列中等待被喚醒。
可重入
ReentrantLock提供的是可重入鎖,當線程擷取到鎖時,當再次擷取鎖時仍然可以擷取到,該實作是通過AQS中的state屬性來完成的,每擷取一次鎖則将state加一,沒釋放一次鎖則将state減一。當state為0後則完全釋放鎖。
允許中斷
當線程沒有擷取到鎖時進入同步隊列等待時,則線程可以被中斷,當線程被再次喚醒時,則将線程狀态修改為取消狀态,并且抛出異常,此時就需要使用trycatch進行捕獲異常。
在介紹完ReentrantLock提供的特性之後,接下來剖析一下其源碼實作。前面的AQS了解以後,ReentrantLock的源碼邏輯就相對的簡單了很多。
Lock接口提供的方法
Lock接口提供一下幾個抽象方法:
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
- lock()方法:争搶鎖資源,擷取到則執行,擷取不到則進入同步隊列并阻塞。
- lockInterruptibly():可中斷的擷取鎖。
- tryLock():嘗試擷取鎖,擷取到鎖則傳回true,擷取不到則傳回false。該方法不會将目前線程添加至同步隊列中。
- tryLock(long time, TimeUnit unit):嘗試擷取鎖,運作等待指定的time時間周期。同tryLock(),隻是允許等待指定的時間。
- unlock():釋放鎖。
ReentrantLock的實作
首先來看看一下ReentrantLock類的内部結構。該類中有三個靜态内部類:
abstract static class Sync extends AbstractQueuedSynchronizer
static final class NonfairSync extends Sync
static final class FairSync extends Sync
通過類的繼承關系可知,Sycn繼承自AbstractQueuedSynchronizer,而NonfairSync 和FairSync 均繼承自Sync,在前面的已經了解到AbstractQueuedSynchronizer是提供了基礎的架構,而具體的功能則依賴于具體的實作,在AbstractQueuedSynchronizer中提供了四個未實作的方法:
protected boolean tryAcquire(int arg)
protected boolean tryRelease(int arg)
protected int tryAcquireShared(int arg)
protected boolean tryReleaseShared(int arg)
而這個四個方法實作不同則提供了不同的功能,ReentrantLock是獨占鎖,是以這裡沒有重寫共享相關的方法。這裡的Sycn抽象類中定義了獨占鎖公用的方法,如資源釋放等。
而NonfairSync 和FairSync 則實作了公平鎖和非公平鎖的實作。
在了解了ReentrantLock的類結構以後,接下來從Sycn類的屬性方法說起。
Sycn
- 非公平擷取鎖資源。
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; }
該方法為非公平鎖搶占資源
1>判斷目前state是否為0,為0則通過compareAndSetState(0, acquires)方法設 置state為,成功則将目前線程設定為獨占。
2>State不為0則判斷獨占線程是否是目前線程,是則将state+acquires指派給state。
3>都不滿足則傳回false。
- 釋放鎖資源。
protected final boolean tryRelease(int releases) { int c = getState() - releases; if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; if (c == 0) { free = true; setExclusiveOwnerThread(null); } setState(c); return free; }
- 擷取條件隊列。
final ConditionObject newCondition() { return new ConditionObject(); }
-
lock()擷取鎖的抽象方法。
其他方法相對來說簡單,就不一一列舉了。
FairSync
公平鎖的實作,主要就是實作資源搶占方法tryAcquire()和lock()方法。
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;
}
嘗試搶占資源
-
判斷state是否為0,為0則。
1>判斷同步隊列中否還具有隊列(公平鎖的展現)
2>嘗試修改state的值
如果1.2條件滿足則搶占資源成功,将獨占線程修改為目前線程,失敗則傳回false。
- state不為0,則判斷獨占線程是否是目前線程,是目前線程則将state修改為 state+acquires的值。
- 都不成功則傳回false。
公平鎖擷取鎖方法:
final void lock() {
acquire(1);
}
調用了AQS的acquire方法,在AQS的acquire方法中調用FairSync的tryAcquire實作方法進行鎖的搶占,沒有擷取到鎖則進入同步隊列中等待喚醒。
NonfairSync
非公平鎖的lock方法及tryAcquire方法。
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
可以看到NonfairSync的lock方法不是直接調用acquire方法進行鎖資源擷取的,而是直接嘗試擷取鎖。擷取失敗後才通過acquire方法進行鎖資源的擷取或者進入同步隊列。
tryAcquire實作:
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
可以看到調用了自己的nonfairTryAcquire()方法,該方法繼承自Sync,實作了非公平擷取鎖的邏輯。
在Sync、NonfairSync及FairSync的實作後,再來看一下ReentrantLock的實作。
ReentrantLock
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
兩個構造方法,可以看到ReentrantLock預設是非公平鎖。
關于其對于Lock接口的實作都是調用其屬性sycn的方法,就不一一列舉了。關于ReentrantLock實作的線程間的通信Condition及ConditionObject就不再進行梳理了,因為都是調用了AQS裡的方法,這些方法在前面的文章中已經梳理過了。