AbstractQueuedSynchronizer源碼分析(二):獨佔鎖的獲取與釋放
1、典型實現:ReentrantLock
ReentrantLock就是一個典型的不響應中斷的獨佔鎖,那就從ReentrantLock的lcok()開始走讀不響應中斷的獨佔鎖的實現邏輯。
這裏我們基於ReentrantLock默認的非公平鎖的lock(),公平鎖我們留待分析ReentrantLock的源碼中進行詳細分析。
先上NonfairSync代碼
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* 立即嘗試獲取鎖,失敗後再去走正常的鎖獲取流程。
* (非公平不需要根據隊列順序來獲取鎖,直接嘗試獲取鎖可以很高的提升鎖的效率)
*/
final void lock() {
//0表示還未有線程獲得鎖
if (compareAndSetState(0, 1))
//設置獨佔鎖的擁有者
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
NonfairSync的類關係是NonfairSync extends Sync,Sync extends AbstractQueuedSynchronizer。這裏採用的模板模式進行設計的,AbstractQueuedSynchronizer的acquire(int arg)方法已經完成了不響應中斷的獨佔鎖獲取邏輯,而tryAcquire(int acquires)是對state鎖標識位的管理,需要子類實現。
2、AbstactQueuedSynchronizer不響應中斷的獨佔鎖方法介紹
AbstactQueuedSynchronizer中供子類實現的方法
方法名 | 作用 |
---|---|
boolean tryAcquire(int arg) | 嘗試以獨佔模式獲取。此方法應該查詢對象的state是否允許以獨佔模式獲取它,如果允許,那麼可以獲取它。 |
boolean tryRelease(int arg) | 嘗試以獨佔模式設置state來反映對鎖的釋放。 |
boolean isHeldExclusively() | 同步器是否在獨佔模式下被當前線程佔用。 |
AbstactQueuedSynchronizer中實現的方法
方法名 | 作用 |
---|---|
void acquire(int arg) | 在獨佔模式下獲取鎖,不響應中斷 |
Node addWaiter(Node mode) | 以mode模式爲當前線程創建node節點,並且加入CLH隊列中。 |
boolean compareAndSetTail(Node expect, Node update) | CAS更新tail屬性 |
Node enq(final Node node) | 將node節點加入隊尾,若隊列未初始化,先進行初始化,在同步器整個生命週期中只會初始化一次 |
boolean compareAndSetHead(Node update) | CAS更新head屬性 |
boolean acquireQueued(final Node node, int arg) | 以獨佔不響應中斷模式爲已在隊列中的線程獲取鎖 |
void setHead(Node node) | 把node設爲CLH隊列的head節點,並將node節點thread釋放、把原head節點從CLH隊列中釋放 |
boolean shouldParkAfterFailedAcquire(Node pred, Node node) | 尋找安全點,當找到安全點後進行阻塞 |
void LockSupport.park(Object blocker); | 當前線程阻塞 |
boolean compareAndSetWaitStatus(Node node,int expect,int update) | CAS更新waitStatus屬性 |
boolean parkAndCheckInterrupt() | 阻塞當前線程,並在喚醒後返回中斷狀態 |
void cancelAcquire(Node node) | 取消線程獲取鎖 |
boolean compareAndSetNext(Node node,Node expect,Node update) | CAS更新next屬性 |
void unparkSuccessor(Node node) | 喚醒後繼節點中的線程 |
boolean compareAndSetState(int expect, int update) | CAS更新state屬性 |
void selfInterrupt() | 標記當前線程中斷狀態 |
- CAS方法介紹
在上面的方法中,我們發現#compareAndSetTail,#compareAndSetHead,#compareAndSetWaitStatus,#compareAndSetNext,#compareAndSetState方法都是CAS方法。拉出源碼一看,方法體內部是通過調用unsafe#compareAndSwapObject方法屬性的更新。
關於CAS的介紹,可以參考【Java中的CAS應用】
下面源碼中,展示了AQS那些屬性可以進行CAS操作。
private static final Unsafe unsafe = Unsafe.getUnsafe();
// AbstractQueuedSynchronizer.class的屬性
// state屬性下標,以下雷同
private static final long stateOffset;
private static final long headOffset;
private static final long tailOffset;
// Node.class的屬性
// waitStatus屬性下標,以下雷同
private static final long waitStatusOffset;
private static final long nextOffset;
static {
try {
// 獲取屬性state在AbstractQueuedSynchronizer類中的下標,以下雷同
stateOffset = unsafe.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("state"));
headOffset = unsafe.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("head"));
tailOffset = unsafe.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("tail"));
waitStatusOffset = unsafe.objectFieldOffset
(Node.class.getDeclaredField("waitStatus"));
nextOffset = unsafe.objectFieldOffset
(Node.class.getDeclaredField("next"));
} catch (Exception ex) { throw new Error(ex); }
}
private final boolean compareAndSetHead(Node update) {
return unsafe.compareAndSwapObject(this, headOffset, null, update);
}
private final boolean compareAndSetTail(Node expect, Node update) {
return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
}
private static final boolean compareAndSetWaitStatus(Node node, int expect, int update) {
return unsafe.compareAndSwapInt(node, waitStatusOffset, expect, update);
}
private static final boolean compareAndSetNext(Node node, Node expect, Node update) {
return unsafe.compareAndSwapObject(node, nextOffset, expect, update);
}
- acquire(int arg)方法源碼及註釋
// 在獨佔模式下獲取鎖,忽略中斷;參數arg傳輸給#tryAcquire方法,可以表示任何你想要表達的意思。
public final void acquire(int arg) {
// 1、tryAcquire(arg),調用同步器實現的tryAcquire獲取鎖,返回true流程結束,否則進入2
// 2、addWaiter(Node.EXCLUSIVE),將當前線程封裝成node節點,加入到CLH隊列尾部,進入3
// 3、若線程從queued中被喚醒時,返回中斷狀態,若爲true,進入4
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
// 4、將中斷狀態傳輸給當前線程
selfInterrupt();
}
/**
* 這裏的tryAcquire需要AQS的子類(同步器)進行實現,提供給父類的模板方法調用。
* return 返回true表示獲取鎖成功,否則表示獲取鎖失敗
*/
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
- addWaiter(Node mode) 方法源碼及註釋
/**
* 以mode模式爲當前線程創建node節點,並且加入CLH隊列中。
*
* @param mode Node.EXCLUSIVE 獨佔模式, Node.SHARED 共享模式
* @return 創建的node節點
*/
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// node節點嘗試快速入隊,若失敗進入enq()方法
Node pred = tail;
if (pred != null) {
// 設置node節點的前驅,無多線程併發,放在CAS外
node.prev = pred;
if (compareAndSetTail(pred, node)) {
// 存在多個線程併發操作pred節點,必須在節點入隊成功後,把node設爲pred節點的後繼,。
// 必須要CAS成功後才能操作。
pred.next = node;
return node;
}
}
// node節點入隊
enq(node);
return node;
}
- enq(final Node node)方法源碼及註釋
/**
* 將node節點加入隊尾,若隊列未初始化,先進行初始化,在同步器的整個生命週期中只會初始化一次
* @param node 加入隊尾的node節點
* @return node節點的前驅節點
*/
private Node enq(final Node node) {
// 通過循環,不斷嘗試將node加入隊尾;直至成功,跳出循環。
for (;;) {
Node t = tail;
if (t == null) {
// 初始化隊列,Node節點中不包含線程
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
// 加入隊尾成功,跳出循環。
t.next = node;
return t;
}
}
}
}
- acquireQueued(final Node node, int arg)源碼及註釋
/**
* 以獨佔不響應中斷模式獲取已在隊列中的線程,直至tryAcquire(arg)方法返回true(獲取鎖成功),並返回中斷狀態。
*
* @param node 當前線程的node節點
* @param arg acquire方法參數
* @return 如果等待時被中斷,返回true,否則返回false
*/
final boolean acquireQueued(final Node node, int arg) {
// 失敗狀態
boolean failed = true;
try {
// 中斷狀態
boolean interrupted = false;
// 進行循環,直至tryAcquire成功後,返回中斷狀態。
for (;;) {
// 獲取前驅節點
final Node p = node.predecessor();
// 1、前驅節點爲head && tryAcquire獲取鎖返回true,返回中斷狀態,否則進入2
if (p == head && tryAcquire(arg)) {
// 將該節點設爲head節點,並將node節點中的線程釋放
setHead(node);
// 釋放前驅節點的引用,幫助GC
p.next = null;
// 標記失敗狀態
failed = false;
return interrupted;
}
// 2、shouldParkAfterFailedAcquire,判斷node節點是否應該阻塞,返回true進入3,否則進入1
// 3、parkAndCheckInterrupt,node節點進入阻塞狀態,被喚醒後返回中斷狀態,返回true,標記interrupted爲true,否則不標記;隨後進入1
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
// 判斷失敗狀態,若爲true;取消node節點acquire。根據上面流程看,個人認爲只有在出現異常時纔會出現cancelAcquire(node)的情況。
if (failed)
cancelAcquire(node);
}
}
- setHead(Node node)源碼及註釋
/**
*把node設爲CLH隊列的head節點,並從CLH隊列中出列。
*
*/
private void setHead(Node node) {
// 設爲head節點
head = node;
// 釋放node節點中線程
node.thread = null;
// 釋放對前驅節點的引用,幫助GC
node.prev = null;
}
- shouldParkAfterFailedAcquire(Node pred, Node node)源碼及註釋
/**
* 獲取鎖失敗後,根據前驅判斷當前節點是否應該進行阻塞。
*
* @param pred 前驅節點
* @param node 當前節點
* @return 返回true,當前線程應該阻塞
*/
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
// 獲取pred節點waitStatus狀態值
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
/*
* 若pred節點爲SIGNAL狀態,表示pred節點釋放鎖時會喚醒(unpark)後繼節點;同時也表示node節點可以安全的進行阻塞(park)了。
*/
return true;
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
/*
* waitStatus>0表示前驅節點被取消,放棄當前的pred節點,不斷循環尋找前驅節點,直至尋找到一個未被取消的節點。
*/
do {
// 1、pred = pred.prev,獲取pred的前驅節點
// 2、node.prev = pred.prev,當前節點指向新的前驅節點
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
// 尋找成功後,前驅節點next引用node節點
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.
*/
/*
* 上面兩種狀態都不滿足時,通過CAS操作更新pred節點爲waitStatus=SIGNAL
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
// 不能park,需要不斷遍歷,直到前驅節點waitStatus=SIGNAL
return false;
}
- LockSupport.park(Object blocker)源碼
具體關於LockSupport的內容,可以參考【LockSupport的使用】,不做贅述。
//這行代碼的作用就是使線程阻塞在這裏,等待其他的線程調用該unpark該線程,喚醒線程後繼續執行後面方法
LockSupport.park(this);
- parkAndCheckInterrupt()源碼及註釋
/**
* 現在阻塞在當前,當前驅線程調用unpark方法後,可以喚醒阻塞,並返回中斷狀態。
*/
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
- cancelAcquire(Node node)源碼及註釋
/**
* Cancels an ongoing attempt to acquire.
*
* @param node the node
*/
/**
* 取消node節點,
* 若node節點的prev是正常節點,連接node.prev和node.next
* 若node節點的prev是非正常狀態的節點,就喚醒node節點的後繼節點
*/
private void cancelAcquire(Node node) {
// Ignore if node doesn't exist
// node不存在,忽略後面邏輯
if (node == null)
return;
// 釋放node節點中線程
node.thread = null;
// Skip cancelled predecessors
// 跳過所有被取消的前驅
Node pred = node.prev;
while (pred.waitStatus > 0)
// 1.pred = pred.prev 將pred的前驅節點設爲pred
// 2.node.prev = pred 將pred設置爲node的前置節點
// 這裏的邏輯等於是放棄原來的pred節點,將pred.prev設爲新的pred
node.prev = pred = pred.prev;
// predNext is the apparent node to unsplice. CASes below will
// fail if not, in which case, we lost race vs another cancel
// or signal, so no further action is necessary.
// 取出pred前驅原值
Node predNext = pred.next;
// Can use unconditional write instead of CAS here.
// After this atomic step, other Nodes can skip past us.
// Before, we are free of interference from other threads.
// 設置node節點狀態爲CANCELLED
node.waitStatus = Node.CANCELLED;
// If we are the tail, remove ourselves.
// 如果node爲tail節點,就將pred設爲tail節點
if (node == tail && compareAndSetTail(node, pred)) {
// 釋放pred節點的next屬性
compareAndSetNext(pred, predNext, null);
} else {
// If successor needs signal, try to set pred's next-link
// so it will get one. Otherwise wake it up to propagate.
// 若node後繼節點需要喚醒,嘗試將pred的next屬性設爲後繼節點。否則就喚醒node節點
int ws;
// 1.pred節點不爲head節點,ture進入2,否則進入6
// 2.pred節點waitStatus爲SIGNAL,true進入4,否則進入3
// 3.當pred未被取消,並將其waitStatus更新爲SIGNAL成功後進入4,否則進入6
// 4.若pred的線程不爲空進入5,否則進入6
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
// 5.若node.next節點是正常節點,將node.next節點地址指向pred.next地址,等於放棄了node節點了。
Node next = node.next;
if (next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next);
} else {
// 6若pred節點不滿足上面的情況,就需要直接喚醒node的後繼節點,即node.next
unparkSuccessor(node);
}
// 幫助GC
node.next = node; // help GC
}
}
- unparkSuccessor(Node node)源碼及註釋
/**
* 喚醒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;
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) {
s = null;
// 若node.next已經被取消,那需要重tail變量找到離node最近的那個節點,作爲node節點後繼節點喚醒
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
// 喚醒node的後繼節點
if (s != null)
LockSupport.unpark(s.thread);
}
- selfInterrupt()源碼及註釋
/**
* Convenience method to interrupt current thread.
*/
/**
* 將中斷狀態傳輸給當前線程
*/
static void selfInterrupt() {
Thread.currentThread().interrupt();
}
上面代碼走讀添加了很多註釋,能夠清晰的理解到不響應中斷鎖的獲取方式。以上的註釋和理解的
篇幅限制,相關的知識點就貼上相關鏈接了,也是我在走讀代碼時遇到的不懂,然後去了解的。若讀者朋友們對其不是很熟悉,也可以先閱讀相關文章,主要是【CAS】和【LockSupport】。
不響應中斷的獲取獨佔鎖的流程圖
附上不響應中斷的獲取獨佔鎖的流程圖
3、AbstactQueuedSynchronizer響應中斷的獨佔鎖方法
在介紹響應中斷的獨佔鎖之前,需要了解什麼是【中斷】,在瞭解中斷後。我們繼續看acquireInterruptibly(int arg) throws InterruptedException方法,對比acquire(int arg)發現,acquireInterruptibly方法在獲取到線程中斷標記後,立即拋出InterruptedException異常以做響應。
方法名 | 作用 |
---|---|
void acquireInterruptibly(int arg) throws InterruptedException | 嘗試回去哦響應中斷的獨佔鎖 |
doAcquireInterruptibly(int arg) throws InterruptedException | 將線程加入CLH隊列中進行管理 |
與不響應中斷鎖不同主要是以上兩個方法,其他相同方法不在此贅述。
- void acquireInterruptibly(int arg) throws InterruptedException 若線程被標記中斷,直接拋出中斷異常
public final void acquireInterruptibly(int arg)
throws InterruptedException {
// 線程中斷立即響應
if (Thread.interrupted())
throw new InterruptedException();
// 線程獲取獨佔鎖失敗後,進行入隊操作
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}
- void doAcquireInterruptibly(int arg) throws InterruptedException 若線程被標記中斷,直接拋出中斷異常
private void doAcquireInterruptibly(int arg)
throws InterruptedException {
// 入隊
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
// 直至獲取到鎖或線程被標記中斷
for (;;) {
// head節點(dummy)的next可以嘗試獲取鎖
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
// node成爲新的head節點
setHead(node);
p.next = null; // help GC
failed = false;
return;
}
// 1、尋找安全點,成功後進入2,否則再次進入循環
// 2、進入阻塞,阻塞被喚醒後返回中斷狀態,若被標記中斷,進入3,否則再次進入循環
// 3、拋出中斷異常
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
// 拋出中斷異常後,取消node節點
if (failed)
cancelAcquire(node);
}
}
4、AbstactQueuedSynchronizer獨佔鎖的釋放
方法名 | 作用 |
---|---|
boolean release(int arg) | 釋放獨佔模式下的鎖 |
void unparkSuccessor(Node node) | 喚醒後繼節點中的線程 |
- release(int arg)源碼及註釋
public final boolean release(int arg) {
// 調用子類實現tryRelease方法,返回ture表示release成功,否則結束流程
if (tryRelease(arg)) {
// 釋放鎖成功,喚醒隊列中第一個node節點中的線程,因爲head節點是dummy節點,所以喚醒head.next節點中線程。
Node h = head;
// 此時的head節點的waitStatus應該爲-1,是在調用acquire時設置的。
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
5、總結
從源碼的走讀和源碼註解理解,瞭解到AbstactQueuedSynchronizer獨佔鎖如何管理線程,如何阻塞,阻塞在何處,何時被喚醒,響應中斷和不響應中斷如何管理中斷狀態。定義了從嘗試獲取–阻塞–喚醒–嘗試獲取的整個流程。另外也知道鎖的狀態(state)管理完全是依賴子類對tryAcquire(int arg)和tryRelease(int arg)的實現。換句話說鎖狀態(state)是交由子類實現管理,AQS並不關心鎖的管理,它只關心Thead何時如何怎樣去獲取鎖。
下一章節介紹共享鎖,請各位讀者持續關注。