ReentrantLock lock = new ReentrantLock(true);
lock.lock();
調用lock()方法實際調用sync內部類的lock方法,Sync類的的lock方法爲抽象方法,實際調用其子類的lock方法,由於創建的是公平鎖,所以,最終調用FairSync的lock方法
public void lock() {//ReentrantLock#lock
sync.lock();//調用公平鎖的lock
}
FairSync的lock方法中調用了acquire方法,嘗試獲取鎖,acquire()是AQS(AbstractQueuedSynchronizer)中的實現
Sync繼承自AQS,FairSync繼承自Sync
final void lock() {//FairSync#lock
acquire(1);
}
AQS的acquire方法,先嚐試去獲取一次鎖,如果獲取失敗,將該線程封裝爲node節點,添加到AQS阻塞隊列的隊尾,並以自旋的方式去嘗試獲取鎖,如果在獲取鎖的過程中出現異常,自我中斷(selfInterrupt())
public final void acquire(int arg) {//arg=1
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
//acquireQueued的返回值實際是該線程在阻塞中是否有被中斷,如果被中斷了,返回true,此時再進行中斷
selfInterrupt();
}
先看selfInterrupt(),調用了當前線程的interrupt()方法進行中斷
private static void selfInterrupt() {
Thread.currentThread().interrupt();
}
再回過頭看acquire方法中的tryAcquire方法,該方法用來先去嘗試能否獲取鎖,該方法是需要AQS的子類去重寫的,AQS中的此方法,只拋出UnsupportedOperationException異常
FairSync#tryAcquire,先判斷資源狀態,如果未被持有,則再判斷是否有線程在等待資源,如果是,獲取鎖失敗,如果沒有線程等待,嘗試持有;如果資源以及被線程持有,判斷持有者是不是自己,如果是自己,重入數+1,否則獲取失敗
protected final boolean tryAcquire(int acquires) {//acquires=1
final Thread current = Thread.currentThread();//獲取當前線程
int c = getState();//獲取資源狀態
if (c == 0) {//資源沒有被鎖定
if (!hasQueuedPredecessors() &&//判斷在此線程前是否有等待時間更長的線程,因爲是公平鎖,所以如果有線程在配對等待獲取鎖,則該線程不能立即獲得鎖
compareAndSetState(0, acquires)) {//CAS操作,如果沒有等待的線程,嘗試去獲取鎖
setExclusiveOwnerThread(current);//沒有等待線程,當前線程獲取了鎖,那麼將資源持有者設置爲此線程
return true;
}
}
else if (current == getExclusiveOwnerThread()) { //資源被鎖定,判斷當前線程是否是鎖持有者,如果是,重入數+1
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;//資源狀態不爲0,其持有者也不是該線程,則獲取失敗
}
下面是判斷線程前是否有其他線程等待資源的方法:hasQueuedPredecessors()
/**
* Queries whether any threads have been waiting to acquire longer
* than the current thread.
*/
public final boolean hasQueuedPredecessors() {
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
Node t = tail; //tail阻塞隊列的隊尾節點
Node h = head;//head:阻塞隊列的隊首節點
Node s;
// h != t 頭節點和隊尾節點不是同一個(只有當阻塞隊列中沒有節點時,tail和head都指向null)
//(s = h.next) == null 判斷頭節點的下一個節點是否存在
//s.thread != Thread.currentThread() 如果頭結點的下一個節點操作,判斷這個節點裏的線程是否爲當前線程
//AQS中的阻塞隊列是以Node爲節點的雙鏈表結構
//其頭節點是一個僞節點,頭節點的下一個節點即隊首節點是存放第一個阻塞線程的節點
//所以源碼中用頭節點的next節點中的線程與當前線程做比較
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
到此,tryAcquire()的邏輯已全部分析,接下載再看如果獲取鎖失敗後的處理
獲取鎖失敗,先將線程包裝成node節點,添加到阻塞線程的隊尾
/**
* Creates and enqueues node for current thread and given mode.
*/
private Node addWaiter(Node mode) {//AbstractQueuedSynchronizer#addWaiter
Node node = new Node(Thread.currentThread(), mode);//將線程包裝成Node對象
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;//獲取隊尾節點
if (pred != null) {//如果CLH隊列不爲空,將新節點加入到隊尾
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//只有當阻塞隊列爲空時,纔會調用此方法來添加節點
enq(node);//如果CLH隊列爲空,自旋的方式添加節點(head節點和該線程節點)
return node;
}
/**
* Inserts node into queue, initializing if necessary. See picture above.
*/
private Node enq(final Node node) {//AbstractQueuedSynchronizer#enq
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
//第一次遍歷
//判斷尾節點爲空,說明整個隊列是空的,添加頭節點,將隊頭和隊尾都指向此節點
if (compareAndSetHead(new Node()))
tail = head;
} else {
//第二次遍歷
//新增節點的pre節點指向當前隊列的隊尾節點
//將自己設置爲隊尾節點,將隊尾節點的next節點指向自己
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
將線程添加到隊尾後,接下來線程以自旋的方式嘗試獲取鎖
/**
* Acquires in exclusive uninterruptible mode for thread already in
* queue. Used by condition wait methods as well as acquire.
*/
final boolean acquireQueued(final Node node, int arg) {//AbstractQueuedSynchronizer#acquireQueued
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();//獲取當前線程節點的上一個節點
if (p == head && tryAcquire(arg)) {
//判斷他的上一個節點是否是頭節點,如果是,就再次嘗試獲取鎖
//如果獲取鎖成功了,把自己置爲頭節點
//上面有提到,頭節點是僞節點,隊列中真正的第一個阻塞線程實際在第二個節點
//所以,判斷如果自己的上一個節點是頭節點,說明自己就是第一個被阻塞的線程,就可以去獲取鎖了
setHead(node);//prev,thread的引用都指向null
p.next = null; // help GC
failed = false;
return interrupted;
}
//不是頭節點,或者獲取鎖失敗了,進入一下邏輯
//1.確保這個節點前的節點的狀態是SIGNAL,這樣自己才能安心的被阻塞,因爲會被前一個節點叫醒
//2.調用LockSupport.park對線程阻塞
//3.在線程被喚醒時,返回這個線程的中斷狀態
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
//如果線程在阻塞期間被中斷後,不響應中斷,只是設置一個是否中斷的變量爲true
//最後在成功獲取鎖後返回這個中斷狀態給其調用者
interrupted = true;
}
} finally {
//最後在返回的時候判斷下獲取鎖的這個自旋過程中是否有發生異常,如果有,則將節點從隊列中移除
if (failed)
cancelAcquire(node);
}
}
做阻塞前的保障工作
/**
* Checks and updates status for a node that failed to acquire.
* Returns true if thread should block. This is the main signal
* control in all acquire loops. Requires that pred == node.prev.
*/
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
//判斷上個節點的狀態,只有在狀態是SIGNAL的時候,才能確保上一個線程用完鎖後會通知自己來獲取鎖
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
if (ws > 0) {
//ws > 0,即CANCEL狀態,將自己前面所有是此狀態的線程節點全部移除隊列
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
//如果是其他的狀態,將節點的狀態改爲SIGNAL
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
阻塞線程
/**
* Convenience method to park and then check if interrupted
*
* @return {@code true} if interrupted
*/
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);//此方法調用後,線程會在這裏阻塞,直到上一個線程喚醒它
return Thread.interrupted();
}
再回頭看acquireQueued()的方法中的cancelAcquire()方法,此方法我的理解是在acquireQueued()出現異常時纔會調用,因爲如果正常執行,當輪到此線程獲取鎖,併成功的獲取鎖後,會將failed置爲false,這樣的話是不會觸發cancelAcquire()方法的,而且此方法的出口,只有成功獲取鎖後的一個出口
/**
* Cancels an ongoing attempt to acquire.
*
* @param node the node
*/
private void cancelAcquire(Node node) {
// Ignore if node doesn't exist
if (node == null)
return;
node.thread = null;//釋放線程引用
Node pred = node.prev;
while (pred.waitStatus > 0)//讓節點前所有CANCEL狀態的節點退出隊列
node.prev = pred = pred.prev;
Node predNext = pred.next;
node.waitStatus = Node.CANCELLED;
//如果該節點在隊尾,直接將隊尾置爲他前面第一個不是CANCEL的節點
if (node == tail && compareAndSetTail(node, pred)) {
compareAndSetNext(pred, predNext, null);
} else {
int ws;
//pred != head 他前面第一個不是CANCEL的節點不是頭節點
//(ws = pred.waitStatus) == Node.SIGNA 他前面第一個不是CANCEL的節點的狀態是SIGNAL
//ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL) 他前面第一個不是CANCEL的節點的狀態不是SIGNAL則置爲SIGNAL
//pred.thread != null 他前面第一個不是CANCEL的節點中有持有線程
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
//如果pred不是頭節點,且保證他的狀態是SIGNAL,且他持有線程,將pred.next的與node節點的next節點連接
Node next = node.next;
if (next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next);
} else {
unparkSuccessor(node);
}
node.next = node; // help GC
}
}
喚醒隊列中的第一個線程
/**
* Wakes up node's successor, if one exists.
*
* @param node the node
*/
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus;
//把節點狀態改爲0--初始狀態
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
Node s = node.next;
if (s == null || s.waitStatus > 0) {
//檢查節點下一個節點,如果爲null,或者狀態是CANCEL,從隊尾開始,找到第一個有效的節點
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);//喚醒隊列的第一個線程
}
到此,公平鎖的lock()方法全部結束
總述
調用公平鎖的lock方法,實際調用ReentrantLock中FairSync的lock()方法
他會先去調用tryLock()方法看能否先獲取鎖,如果獲取不到,將線程添加到CLH隊列裏,以自旋的方式再去獲取鎖,獲取成功後判斷自己是否被中斷過,然後再次中斷自己
在自旋的過程中爲了保證自己能被成功喚醒,他回去判斷自己前面節點的狀態,如果是SIGNAL,那麼自己會被成功喚醒,如果是CANCEL狀態,需要將節點從隊列中移除,如果是其他的狀態,將狀態置爲SIGNAL
如果在獲取鎖的過程中出現了異常,將其從隊列中移除,如果它的頭節點,需要將它的下一個節點線程喚醒