一、AbstractQueuedSynchronizer
屬性
屬性 | 類型 | 作用 |
---|---|---|
state | int | 表示是否被線程持有,0表示沒有,n表示線程重入次數 |
head | Node | 線程阻塞隊列的頭節點,head表示正在持有鎖的線程 |
tail | Node | 線程阻塞隊列的尾節點,未競爭到鎖的Node將添加至隊列尾部 |
unsafe | Unsafe | 含有CAS原子操作的類,調用本地方法實現 |
exclusiveOwnerThread | Thread | 繼承AbstractOwnableSynchronizer類的字段,表示當前持有鎖的線程 |
Node 屬性
屬性 | 類型 | 作用 |
---|---|---|
thread | Thread | 線程對象 |
prev | Node | 指向隊列中前一個節點 |
next | Node | 指向隊列中後一個節點 |
waitStatus | int | 等待狀態,1表示線程取消等待,-1表示後一個節點該被喚醒,-2,-3暫時不用 |
鎖實現的思路:
1 CAS操作更新state,比如將0更新爲1,成功表示競爭到了鎖,操作失敗則競爭失敗,需要加入阻塞隊列
2 將線程對象封裝爲Node,加入隊列尾部,可能同時有多個線程操作,所以這一步也是CAS操作
3 解鎖操作將state更新爲0,喚醒隊列中下一個線程Node,解鎖的線程Node從隊列中移除
二、ReentrantLock
ReentrantLock 中有個 Sync 屬性,Sync 抽象類繼承自 AQS,在 ReentrantLock 內部有非公平(NonfairSync)和公平(FairSync)兩種實現,先學習 FairSync。
結合場景理解代碼:
線程 t1 首次申請
假設只有線程t1獲取鎖,執行lock方法:
FairSync#lock
final void lock() {
acquire(1);
}
AbstractQueuedSynchronizer#acquire
public final void acquire(int arg) {
// 嘗試獲取鎖
// 獲取失敗表示競爭且失敗,加入隊列中(具體邏輯後面分析)
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
FairSync#tryAcquire
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// state=0表示可競爭狀態
if (c == 0) {
// 如果隊列中沒有等待線程(公平鎖,如果隊列有等待的線程,當前線程就沒有資格獲取鎖了)
// 再CAS操作更新state,失敗說明同一時間其他線程獲取到了鎖
// 成功設置獨佔標記爲當前線程
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;
}
// 返回fasle表示鎖已經被持有或者競爭失敗
return false;
}
如果只有一個線程t1獲取鎖,則在tryAcquire方法CAS這一步成功獲取到鎖。
線程 t2 獲取已被佔有的鎖
如果在 t1持有鎖 的狀態下,t2線程獲取鎖,執行tryAcquire一定是失敗的,這時會執行acquireQueued(addWaiter(Node.EXCLUSIVE), arg)這一句。
AbstractQueuedSynchronizer#addWaiter
private Node addWaiter(Node mode) {
// 將當前線程封裝在Node中,構造方法第二參數表示是獨佔還是共享
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
// 如果隊尾不爲空,CAS更新tail,成功則將調整隊列指向至正確,返回當前node
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
// 如果tail指向是空或者CAS更新失敗執行enq
enq(node);
return node;
}
AbstractQueuedSynchronizer#enq
private Node enq(final Node node) {
// 循環
for (;;) {
Node t = tail;
// 如果tail爲null說明隊列爲空,初始化隊列,這裏也會存在競爭所以依然CAS操作
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
// CAS更新隊尾Node直到成功,返回隊尾當前線程node
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
這時隊列爲空,需要初始化隊列,然後t2線程node加入隊列:
AbstractQueuedSynchronizer#acquireQueued
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
// 獲取前一個node
final Node p = node.predecessor();
// 前一個node是頭,嘗試獲取鎖,如果成功當前node成爲頭,解放原head
// 嘗試獲取失敗說明還有線程在持有
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
// 不是head或者獲取鎖失敗,設置前個node中waitState標記爲-1即等待狀態
if (shouldParkAfterFailedAcquire(p, node) &&
// 已經是-1時,停止當前線程
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
// 執行到這裏說明出現異常,取消獲取,後面再分析
cancelAcquire(node);
}
}
AbstractQueuedSynchronizer#shouldParkAfterFailedAcquire
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
// 如果-1表示下個節點需要喚醒
if (ws == Node.SIGNAL)
return true;
// 如果1表示線程取消鎖競爭,將其這些移除,直到找到一個不是取消狀態的指向自己
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
// 0 -2 -3 則將設置爲-1
} else {
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
AbstractQueuedSynchronizer#parkAndCheckInterrupt
private final boolean parkAndCheckInterrupt() {
// 停止當前線程,底層用了unsafe.park
LockSupport.park(this);
// 返回線程中斷狀態,先不管
return Thread.interrupted();
}
在 線程t1 還在持有鎖的情況下,上面代碼的目的是將當前線程node的前驅node狀態(waitState)設置爲-1,表示下一個node的線程需要被喚醒。同時將當前線程停止:
假設在 線程t1 在持有鎖狀態下,又來一個線程:
線程 t1 解鎖
解鎖調用unlock方法,同一時間只會有一個線程解鎖
AbstractQueuedSynchronizer#release
public final boolean release(int arg) {
// 嘗試釋放,如果成功,喚醒隊列中下一個線程
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
Sync#tryRelease
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
// 如果解鎖的線程不是佔有鎖的線程,拋出非法異常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// state爲0表示沒有線程佔有鎖,設置exclusiveOwnerThread爲null表示沒有線程佔有
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
// 更新state
setState(c);
return free;
}
AbstractQueuedSynchronizer#unparkSuccessor
private void unparkSuccessor(Node node) {
// 這裏node是head,如果waitState<0,更新狀態爲0
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
// 可能隊列中有取消的節點,從後往前找到可以需要喚醒的node,忽略已取消的node,爲什麼從後向前?
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;
}
// 如果head的後一個節點不是null,說明隊列有等待的線程,將其喚醒
if (s != null)
LockSupport.unpark(s.thread);
}
隊列中的線程在 AbstractQueuedSynchronizer#acquireQueued 方法的 parkAndCheckInterrupt 方法中阻塞,就是那個for循環,被喚醒後會繼續循環。被喚醒的線程獲取到鎖併成爲head。
for (;;) {
// 前驅node爲head並且獲取到鎖,當前線程競升爲head
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;
}
AbstractQueuedSynchronizer#setHead
private void setHead(Node node) {
head = node;
node.thread = null;
node.prev = null;
}
線程t1 解鎖,喚醒 線程t2 後:
爲什麼從後向前遍歷找需要喚醒的線程?
1 首先滿足 (s == null || s.waitStatus > 0) 纔會進入遍歷,如果head的next節點不是null且waitState<=0就會直接喚醒next的線程。
2 head.next(即s)什麼時候會是null?
node.prev = t;
if (compareAndSetTail(t, node)) { // 1
t.next = node; // 2
return t;
}
在enq方法和addWaiter方法中都有上面代碼片段,t表示tail的node。先將當前node指向tail,CAS更新隊尾node,成功後將tail的next指向當前node。整個操作不是原子的,有可能出現下面兩種情況:
如果類似圖中第二種情況,那麼head.next爲null,但是其實隊列中有等待線程,所以需要從隊尾在遍歷到當前node看看有沒有需要喚醒的node。
如何公平
在 FairSync#tryAcquire 方法中嘗試加鎖時,需要先判斷隊列中是否有線程排隊,如果沒有才進行CAS更新state。先到隊列的線程線程先獲得鎖。
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
AbstractQueuedSynchronizer#hasQueuedPredecessors
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; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
head == tail 情況有以下兩種情況,一是隊列沒有初始化過都是null,二是隊列中保留一個thread爲null,waitState爲0的node,這兩種情況都表示隊列中已經沒有排隊線程。
還有一種情況就是head.next的線程和當前線程是同一個,返回也是false,因爲滿足可重入的條件。
取消獲取鎖
在acquireQueued方法try-finally的finally代碼塊中有取消獲取鎖的操作,正常情況不會執行,當有異常時執行。**可能發生的異常就是超過了最大鎖計數 和 隊列中當前線程的前驅爲null。**個人認爲兩種都不可能出現(可能太笨了看不懂)。那就看看怎麼取消的吧。
AbstractQueuedSynchronizer#cancelAcquire
private void cancelAcquire(Node node) {
// Ignore if node doesn't exist
if (node == null)
return;
node.thread = null;
// 找到前面不是取消狀態的node
Node pred = node.prev;
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
Node predNext = pred.next;
// 設置當前node的狀態爲1
node.waitStatus = Node.CANCELLED;
// 如果當前node是tail則移除,同時移除了其他取消狀態的node
if (node == tail && compareAndSetTail(node, pred)) {
compareAndSetNext(pred, predNext, null);
} else {
// 這段有點複雜,沒太看懂,我理解的是從隊列中移除,如果前驅是head則喚醒後續node的線程
// If successor needs signal, try to set pred's next-link
// so it will get one. Otherwise wake it up to propagate.
int ws;
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
Node next = node.next;
if (next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next);
} else {
unparkSuccessor(node);
}
node.next = node; // help GC
}
}
如何非公平
ReentrantLock默認是非公平實現的,Sync的實現類爲NonfairSync,相較與FairSync其實也只有lock時一點不同:
1 lock方法首先CAS操作嘗試獲取鎖,如果失敗在調用acquire方法
2 tryAcquire中調用nonfairTryAcquire方法,獲取鎖時不考慮隊列中是否有等待線程,直接參與競爭
NonfairSync#lock
final void lock() {
// 直接嘗試獲取鎖
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
Sync#nonfairTryAcquire
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;
}
如果tryAcquire失敗也需要進入隊列,與公平鎖一樣。可能出現隊列中線程一直獲取不到鎖,造成鎖飢餓現象。
(20190905)