ReentrantLock
是Java
在JDK1.5
引入的顯式鎖,在實現原理和功能上都和內置鎖(synchronized)上都有區別,在文章最後咱們再比較這兩個鎖。
首先咱們要知道ReentrantLock
是基於AQS
實現的,因此咱們得對AQS
有所瞭解才能更好的去學習掌握ReentrantLock
,關於AQS
的介紹能夠參考我以前寫的一篇文章《一文帶你快速掌握AQS》,這裏簡單回顧下AQS
。java
AQS
即AbstractQueuedSynchronizer
的縮寫,這個是個內部實現了兩個隊列的抽象類,分別是同步隊列和條件隊列。其中同步隊列是一個雙向鏈表,裏面儲存的是處於等待狀態的線程,正在排隊等待喚醒去獲取鎖,而條件隊列是一個單向鏈表,裏面儲存的也是處於等待狀態的線程,只不過這些線程喚醒的結果是加入到了同步隊列的隊尾,AQS
所作的就是管理這兩個隊列裏面線程之間的等待狀態-喚醒的工做。
在同步隊列中,還存在2
中模式,分別是獨佔模式和共享模式,這兩種模式的區別就在於AQS
在喚醒線程節點的時候是否是傳遞喚醒,這兩種模式分別對應獨佔鎖和共享鎖。
AQS
是一個抽象類,因此不能直接實例化,當咱們須要實現一個自定義鎖的時候能夠去繼承AQS
而後重寫獲取鎖的方式和釋放鎖的方式還有管理state,而ReentrantLock
就是經過重寫了AQS
的tryAcquire
和tryRelease
方法實現的lock
和unlock
。node
經過前面的回顧,是否是對ReentrantLock
有了必定的瞭解了,ReentrantLock
經過重寫鎖獲取方式和鎖釋放方式這兩個方法實現了公平鎖和非公平鎖,那麼ReentrantLock
是怎麼重寫的呢,這也就是本節須要探討的問題。編程
ReentrantLock
繼承自父類
Lock
,而後有
3
個內部類,其中
Sync
內部類繼承自
AQS
,另外的兩個內部類繼承自
Sync
,這兩個類分別是用來
公平鎖和非公平鎖的。
Sync
重寫的方法
tryAcquire
、
tryRelease
能夠知道,
ReentrantLock
實現的是AQS
的獨佔模式,也就是獨佔鎖,這個鎖是悲觀鎖。
ReentrantLock
有個重要的成員變量:bash
private final Sync sync;
複製代碼
這個變量是用來指向Sync
的子類的,也就是FairSync
或者NonfairSync
,這個也就是多態的父類引用指向子類,具體Sycn
指向哪一個子類,看構造方法:併發
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
複製代碼
ReentrantLock
有兩個構造方法,無參構造方法默認是建立非公平鎖,而傳入true
爲參數的構造方法建立的是公平鎖。ide
當咱們使用無參構造方法構造的時候即ReentrantLock lock = new ReentrantLock()
,建立的就是非公平鎖。post
public ReentrantLock() {
sync = new NonfairSync();
}
//或者傳入false參數 建立的也是非公平鎖
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
複製代碼
lock
方法調用CAS
方法設置state
的值,若是state
等於指望值0
(表明鎖沒有被佔用),那麼就將state
更新爲1
(表明該線程獲取鎖成功),而後執行setExclusiveOwnerThread
方法直接將該線程設置成鎖的全部者。若是CAS
設置state
的值失敗,即state
不等於0
,表明鎖正在被佔領着,則執行acquire(1)
,即下面的步驟。nonfairTryAcquire
方法首先調用getState
方法獲取state
的值,若是state
的值爲0
(以前佔領鎖的線程恰好釋放了鎖),那麼用CAS
這是state
的值,設置成功則將該線程設置成鎖的全部者,而且返回true
。若是state
的值不爲0
,那就調用getExclusiveOwnerThread
方法查看佔用鎖的線程是否是本身,若是是的話那就直接將state + 1
,而後返回true
。若是state
不爲0
且鎖的全部者又不是本身,那就返回false
,而後線程會進入到同步隊列中。final void lock() {
//CAS操做設置state的值
if (compareAndSetState(0, 1))
//設置成功 直接將鎖的全部者設置爲當前線程 流程結束
setExclusiveOwnerThread(Thread.currentThread());
else
//設置失敗 則進行後續的加入同步隊列準備
acquire(1);
}
public final void acquire(int arg) {
//調用子類重寫的tryAcquire方法 若是tryAcquire方法返回false 那麼線程就會進入同步隊列
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
//子類重寫的tryAcquire方法
protected final boolean tryAcquire(int acquires) {
//調用nonfairTryAcquire方法
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//若是狀態state=0,即在這段時間內 鎖的全部者把鎖釋放了 那麼這裏state就爲0
if (c == 0) {
//使用CAS操做設置state的值
if (compareAndSetState(0, acquires)) {
//操做成功 則將鎖的全部者設置成當前線程 且返回true,也就是當前線程不會進入同步
//隊列。
setExclusiveOwnerThread(current);
return true;
}
}
//若是狀態state不等於0,也就是有線程正在佔用鎖,那麼先檢查一下這個線程是否是本身
else if (current == getExclusiveOwnerThread()) {
//若是線程就是本身了,那麼直接將state+1,返回true,不須要再獲取鎖 由於鎖就在本身
//身上了。
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
//若是state不等於0,且鎖的全部者又不是本身,那麼線程就會進入到同步隊列。
return false;
}
複製代碼
2
,若是不是則拋出異常。state
的值是否爲0,若是是則表明鎖有沒有重入,而後將鎖的全部者設置成null
且返回true
,而後執行步驟3
,若是不是則表明鎖發生了重入執行步驟4
。state=0
,喚醒同步隊列中的後繼節點進行鎖的獲取。state!=0
,不喚醒同步隊列。public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
//子類重寫的tryRelease方法,須要等鎖的state=0,即tryRelease返回true的時候,纔會去喚醒其
//它線程進行嘗試獲取鎖。
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
protected final boolean tryRelease(int releases) {
//狀態的state減去releases
int c = getState() - releases;
//判斷鎖的全部者是否是該線程
if (Thread.currentThread() != getExclusiveOwnerThread())
//若是所的全部者不是該線程 則拋出異常 也就是鎖釋放的前提是線程擁有這個鎖,
throw new IllegalMonitorStateException();
boolean free = false;
//若是該線程釋放鎖以後 狀態state=0,即鎖沒有重入,那麼直接將將鎖的全部者設置成null
//而且返回true,即表明能夠喚醒其餘線程去獲取鎖了。若是該線程釋放鎖以後state不等於0,
//那麼表明鎖重入了,返回false,表明鎖還未正在釋放,不用去喚醒其餘線程。
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
複製代碼
state
的值,若是state=0
即表明鎖沒有被其它線程佔用(可是並不表明同步隊列沒有線程在等待),執行步驟2
。若是state!=0
則表明鎖正在被其它線程佔用,執行步驟3
。經過步驟2
實現了鎖獲取的公平性,即鎖的獲取按照先來先得的順序,後來的不能搶先獲取鎖,非公平鎖和公平鎖也正是經過這個區別來實現了鎖的公平性。學習
final void lock() {
acquire(1);
}
public final void acquire(int arg) {
//同步隊列中有線程 且 鎖的全部者不是當前線程那麼將線程加入到同步隊列的尾部,
//保證了公平性,也就是先來的線程先得到鎖,後來的不能搶先獲取。
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//判斷狀態state是否等於0,等於0表明鎖沒有被佔用,不等於0則表明鎖被佔用着。
if (c == 0) {
//調用hasQueuedPredecessors方法判斷同步隊列中是否有線程在等待,若是同步隊列中沒有
//線程在等待 則當前線程成爲鎖的全部者,若是同步隊列中有線程在等待,則繼續往下執行
//這個機制就是公平鎖的機制,也就是先讓先來的線程獲取鎖,後來的不能搶先獲取。
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//判斷當前線程是否爲鎖的全部者,若是是,那麼直接更新狀態state,而後返回true。
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
//若是同步隊列中有線程存在 且 鎖的全部者不是當前線程,則返回false。
return false;
}
複製代碼
公平鎖的釋放和非公平鎖的釋放同樣,這裏就不重複。
公平鎖和非公平鎖的公平性是在獲取鎖的時候體現出來的,釋放的時候都是同樣釋放的。ui
ReentrantLock
相對於Synchronized
擁有一些更方便的特性,好比能夠中斷的方式去獲取鎖。this
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
public final void acquireInterruptibly(int arg)
throws InterruptedException {
//若是當前線程已經中斷了,那麼拋出異常
if (Thread.interrupted())
throw new InterruptedException();
//若是當前線程仍然未成功獲取鎖,則調用doAcquireInterruptibly方法,這個方法和
//acquireQueued方法沒什麼區別,就是線程在等待狀態的過程當中,若是線程被中斷,線程會
//拋出異常。
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}
複製代碼
ReentrantLock
除了能以能中斷的方式去獲取鎖,還能夠以超時等待的方式去獲取鎖,所謂超時等待就是線程若是在超時時間內沒有獲取到鎖,那麼就會返回false
,而不是一直"死循環"獲取。
doAcquireNanos
方法使用超時等待的方式獲取鎖。false
,結束循環。這裏調用的是LockSupport.parkNanos
方法,在超時時間內沒有被中斷,那麼線程會從超時等待狀態轉成了就緒狀態,而後被CPU
調度繼續執行循環,而這時候線程已經達到超時等到的時間,返回false。
LockSuport
的方法能響應Thread.interrupt
,可是不會拋出異常
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
//若是當前線程已經中斷了 則拋出異常
if (Thread.interrupted())
throw new InterruptedException();
//再嘗試獲取一次 若是不成功則調用doAcquireNanos方法進行超時等待獲取鎖
return tryAcquire(arg) ||
doAcquireNanos(arg, nanosTimeout);
}
private boolean doAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (nanosTimeout <= 0L)
return false;
//計算超時的時間 即當前虛擬機的時間+設置的超時時間
final long deadline = System.nanoTime() + nanosTimeout;
//調用addWaiter將當前線程封裝成獨佔模式的節點 而且加入到同步隊列尾部
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
//若是當前節點的前驅節點爲頭結點 則讓當前節點去嘗試獲取鎖。
if (p == head && tryAcquire(arg)) {
//當前節點獲取鎖成功 則將當前節點設置爲頭結點,而後返回true。
setHead(node);
p.next = null; // help GC
failed = false;
return true;
}
//若是當前節點的前驅節點不是頭結點 或者 當前節點獲取鎖失敗,
//則再次判斷當前線程是否已經超時。
nanosTimeout = deadline - System.nanoTime();
if (nanosTimeout <= 0L)
return false;
//調用shouldParkAfterFailedAcquire方法,告訴當前節點的前驅節點 我要進入
//等待狀態了,到我了記得喊我,即作好進入等待狀態前的準備。
if (shouldParkAfterFailedAcquire(p, node) &&
nanosTimeout > spinForTimeoutThreshold)
//調用LockSupport.parkNanos方法,將當前線程設置成超時等待的狀態。
LockSupport.parkNanos(this, nanosTimeout);
if (Thread.interrupted())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
複製代碼
咱們知道關鍵字Synchronized
+ Object
的wait
和notify
、notifyAll
方法能實現等待/通知機制,那麼ReentrantLock
是否也能實現這樣的等待/通知機制,答案是:能夠。
ReentrantLock
經過Condition
對象,也就是條件隊列實現了和wait
、notify
、notifyAll
相同的語義。 線程執行condition.await()
方法,將節點1從同步隊列轉移到條件隊列中。
線程執行condition.signal()
方法,將節點1從條件隊列中轉移到同步隊列。
由於只有在同步隊列中的線程才能去獲取鎖,因此經過Condition
對象的wait
和signal
方法能實現等待/通知機制。
代碼示例:
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();
public void await() {
lock.lock();
try {
System.out.println("線程獲取鎖----" + Thread.currentThread().getName());
condition.await(); //調用await()方法 會釋放鎖,和Object.wait()效果同樣。
System.out.println("線程被喚醒----" + Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
System.out.println("線程釋放鎖----" + Thread.currentThread().getName());
}
}
public void signal() {
try {
Thread.sleep(1000); //休眠1秒鐘 等等一個線程先執行
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.lock();
try {
System.out.println("另一個線程獲取到鎖----" + Thread.currentThread().getName());
condition.signal();
System.out.println("喚醒線程----" + Thread.currentThread().getName());
} finally {
lock.unlock();
System.out.println("另一個線程釋放鎖----" + Thread.currentThread().getName());
}
}
public static void main(String[] args) {
Test t = new Test();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
t.await();
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
t.signal();
}
});
t1.start();
t2.start();
}
複製代碼
運行輸出:
線程獲取鎖----Thread-0
另一個線程獲取到鎖----Thread-1
喚醒線程----Thread-1
另一個線程釋放鎖----Thread-1
線程被喚醒----Thread-0
線程釋放鎖----Thread-0
複製代碼
執行的流程大概是這樣,線程t1
先獲取到鎖,輸出了"線程獲取鎖----Thread-0",而後線程t1
調用await
方法,調用這個方法的結果就是線程t1
釋放了鎖進入等待狀態,等待喚醒,接下來線程t2
獲取到鎖,然輸出了"另一個線程獲取到鎖----Thread-1",同時線程t2
調用signal
方法,調用這個方法的結果就是喚醒一個在條件隊列(Condition)的線程,而後線程t1
被喚醒,而這個時候線程t2
並無釋放鎖,線程t1
也就無法得到鎖,等線程t2
繼續執行輸出"喚醒線程----Thread-1"以後線程t2
釋放鎖且輸出"另一個線程釋放鎖----Thread-1",這時候線程t1
得到鎖,繼續往下執行輸出了線程被喚醒----Thread-0
,而後釋放鎖輸出"線程釋放鎖----Thread-0"。
若是想單獨喚醒部分線程應該怎麼作呢?這時就有必要使用多個Condition
對象了,由於ReentrantLock
支持建立多個Condition
對象,例如:
//爲了減小篇幅 僅給出僞代碼
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();
Condition condition1 = lock.newCondition();
//線程1 調用condition.await() 線程進入到條件隊列
condition.await();
//線程2 調用condition1.await() 線程進入到條件隊列
condition1.await();
//線程32 調用condition.signal() 僅喚醒調用condition中的線程,不會影響到調用condition1。
condition1.await();
複製代碼
這樣就實現了部分喚醒的功能。
關於Synchronized
的介紹能夠看《synchronized的使用(一)》、《深刻分析synchronized原理和鎖膨脹過程(二)》
ReentrantLock | Synchronized | |
---|---|---|
底層實現 | 經過AQS 實現 |
經過JVM 實現,其中synchronized 又有多個類型的鎖,除了重量級鎖是經過monitor 對象(操做系統mutex互斥原語)實現外,其它類型的經過對象頭實現。 |
是否可重入 | 是 | 是 |
公平鎖 | 是 | 否 |
非公平鎖 | 是 | 是 |
鎖的類型 | 悲觀鎖、顯式鎖 | 悲觀鎖、隱式鎖(內置鎖) |
是否支持中斷 | 是 | 否 |
是否支持超時等待 | 是 | 否 |
是否自動獲取/釋放鎖 | 否 | 是 |
《Java併發編程的藝術》
深刻理解AbstractQueuedSynchronizer(AQS)
Java 重入鎖 ReentrantLock 原理分析)
原文地址:ddnd.cn/2019/03/24/…