ReentrantLock
(簡稱RLock) 是Java的一種鎖機制。從API上看,RLock提供了公平鎖與非公平鎖,並提供了當前鎖狀態監測的一些接口。其內部是由 FairSync
與 NonFairSync
來實現鎖資源的搶佔與釋放。下面咱們來學習下其源碼。node
首先咱們打開 RLock 的構造函數,源碼以下:c#
private final Sync sync;
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
複製代碼
好的,很直白,根據入參構形成員變量 Sync。默認爲 NonfairSync
。到這裏,咱們遇到了第一個新概念 Sync
。先按住好奇心,咱們先找到 lock()
和unlock()
源碼。以下:bash
public void lock() {
sync.lock();
}
public void unlock() {
sync.release(1);
}
複製代碼
到這裏好像有些明白了,RLock更像是對Sync的進一層封裝,經過多態來實現不一樣的鎖策略。到這裏,我有個疑問,公平鎖和非公平鎖的策略有何不一樣呢?那麼這樣看來,啃透Sync
是關鍵。併發
首先咱們捋一下 Sync
繼承結構。IDEA裏鼠標移到類聲明上,Ctrl+H
便可清晰看到類的繼承結構。 框架
AbstractQueuedSynchronizer
(簡稱AQS)。這個類是JDK併發包中的鎖基類,定義了鎖資源獲取與釋放的框架與基本行爲。這個先略過不談,繼續貫徹第一步,從最直白的方法入手。
咱們回憶一下lock的行爲,咱們調用來獲取鎖,若是其它線程已搶佔到鎖資源,當前線程掛起,直到當前線程獲取到鎖。並且RLock支持重入。 NonfairSync#lock()
與 FairSync#lock()
源碼以下: 函數
NonfairSync
先設置了狀態位,而後調用了
acquire()
。
FairSync
則直接調用了
acquire()
。那麼咱們先從
compareAndSetState()
入手,源碼以下:
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
複製代碼
這個函數定義在 AQS 中,用於設置當前lock的狀態,unsafe呢,是JDK中很是變態的一個工具類,能夠直接操控實例對象所在的內存,同時提供了一些原子操做。具體的後面會再展開介紹,簡而言之,這個函數能夠理解爲:工具
protected final boolean compareAndSetState(int expect, int update) {
synchronized (this.getClass()) {
if (this.state == expect) {
this.state = update;
return true;
} else {
return false;
}
}
}
複製代碼
那麼到這裏咱們好像獲得了第一把鑰匙:lock.state 爲0時,爲空閒,而上鎖請求會將狀態置爲1,而且將exclusiveOwnerThread
設爲當前線程。學習
接下來,咱們來看 acquire()
。繼續跟蹤下去, 的 acquire()
代碼以下:ui
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
複製代碼
唔,好像看不出來什麼東西,那麼繼續跟下去,先從 tryAcquire 開始,NonfairSync
和 FairSync
tryAcquire() 核心代碼以下 this
FairSync
獲取鎖以前會調用
hasQueuedPredecessors()
源碼以下:
/**
* @return {@code true} if there is a queued thread preceding the
* current thread, and {@code false} if the current thread
* is at the head of the queue or the queue is empty
* 兩種狀況返回 false:1. 當前線程在隊頭。2. 隊列爲空
*/
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());
}
複製代碼
那麼咱們拿到了第二把鑰匙: 公平鎖在獲取鎖以前會先去查詢是否有其餘人在等待這把鎖,若是沒有,再嘗試獲取。而非公平鎖則不會詢問
這麼看來,公平鎖 Peace&Love,像民謠,多愁善感,與世無爭。而非公平鎖 Aggressive,像Hip-Hop,張揚自我,銳意進取。 那麼回到 acquire()
,還有一個函數: acquireQueued()
,源碼以下:
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
複製代碼
忽略其餘細節,咱們在 parkAndCheckInterrupt()
函數中找到了 LockSupport.park(this);
。就是他了!這個函數會將指定線程掛起,直至LockSupport.unpark(Thread)
被調用或者發生意外被操做系統 interrupt
。 至此, lock()
的鏈便通了。
咱們再來總結一下:***ReentrantLock
在上鎖時,會根據實例化時指定的策略去獲取鎖,默認爲非公平鎖。若是上鎖成功,鎖狀態值+1(重入,最大次數爲 Integer.MAX_VALUE
),並將鎖持有者設置爲當前線程實例。在 Sync
內部維護了一個隊列,存放了全部上鎖失敗的線程。公平鎖在上鎖前,會檢查在本身前面是否還有其餘線程等待,若是有就放棄競爭,繼續等待。而非公平鎖會抓住每一個機會,無論是否前面是否還有其它線程等待,只顧上鎖***
至於鎖釋放,公平鎖與非公平鎖的行爲就同樣了。核心代碼以下
// ReentrantLock#unlock() 釋放鎖資源
public void unlock() {
sync.release(1);
}
// Sync#release()
public final boolean release(int arg) {
// 重入鎖,狀態計數器減一,爲0時釋放
if (tryRelease(arg)) {
Node h = head;
// 釋放鎖時,從等待隊列中獲取線程並嘗試喚醒
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
// Sync#tryRelease() 狀態計數器減一,爲0時,釋放鎖資源,返回true
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;
}
// Sync#unparkSuccessor() 喚醒等待隊列中的線程,讓他(們)繼續搶佔鎖
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
// 上鎖時向隊列尾部添加元素時,可能會致使隊列處在中間狀態,再從尾部遍歷一次
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
// boomya,合適的線程找到啦,將其喚醒
if (s != null)
LockSupport.unpark(s.thread);
}
複製代碼
總結一下: 線程在釋放鎖時,將狀態計數器減一(重入),當狀態計數器爲0時,鎖可用。此時再從等待隊列中尋找合適的線程喚醒,默認從隊首開始,若是隊列正在更新中,且未找到合適的線程,那麼從隊尾開始尋找。
ReentrantLock
在上鎖時,會根據實例化時指定的策略去獲取鎖,默認爲非公平鎖。若是上鎖成功,鎖狀態值+1(重入,最大次數爲 Integer.MAX_VALUE
),並將鎖持有者設置爲當前線程實例。在 Sync
內部維護了一個隊列,存放了全部上鎖失敗的線程。公平鎖在上鎖前,會檢查在本身前面是否還有其餘線程等待,若是有就放棄競爭,繼續等待。而非公平鎖會抓住每一個機會,無論是否前面是否還有其它線程等待,只顧上鎖ReetrantLock
在釋放鎖時,將狀態計數器減一(重入),當狀態計數器爲0時,鎖可用。此時再從等待隊列中尋找合適的線程喚醒,默認從隊首開始,若是隊列正在更新中,且未找到合適的線程,那麼從隊尾開始尋找。