實現(xiàn)重入鎖ReentrantLock鎖使用到的技術(shù)
- CAS 保證操作原子性
- AQS 帶有頭尾節(jié)點的隊列 鏈表實現(xiàn)
Node {
//Node代表了等待的線程
Node prev 前一個Node
Node next 后面一個Node
}
AQS{
Node head 頭節(jié)點
Node tail 尾結(jié)點
}
- 自旋操作
用到的一些方法的含義(公平鎖實現(xiàn)情況)(代碼為Open-JDK13中的)
- tryAcquire 本線程嘗試獲取鎖,獲取成功返回true
- hasQueuedPredecessors 判斷本線程是否需要排隊,需要排隊返回true
- addWaiter 往隊列中添加一個Node 即添加一個排隊的線程
源碼具體執(zhí)行流程:
步驟一:
步驟二:
步驟三:
這里開始麻煩起來了穿扳,第一步本線程第一次嘗試獲取鎖,調(diào)用 tryAcquire茫藏, 如果獲取成功,則上鎖過程結(jié)束霹琼,如果獲取失敗就執(zhí)行另外兩步刷允。
先看下tryAcquire干什么了(為公平鎖實現(xiàn)的tryAcquire)
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
獲取鎖的狀態(tài)
int c = getState();
如果狀態(tài)為0,說明此時鎖時空閑的
但是注意可能有多個線程同時檢測到鎖空閑冤留,那么就會有多個線程都進(jìn)入下面的IF
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
... 省略一部分代碼碧囊,先不討論
return false;
}
如果是空閑狀態(tài)树灶,那么就要先看看是不是要排隊(因為可能有多個線程同時,非公平鎖就不用檢測要不要排隊)
public final boolean hasQueuedPredecessors() {
Node h, s; h表示頭結(jié)點糯而,s表示后續(xù)節(jié)點
如果h==null 那么就說明此此時隊列都沒創(chuàng)建 本線程是第一個線程天通,說明沒有其他線程,就**不用排隊**
if ((h = head) != null) {
s==null說明下一個沒有 但也可能因為并發(fā)的原因 不能保證后面也是空的
s.waitStatus>0 說明下一個被取消了熄驼,要看后面的是什么情況
if ((s = h.next) == null || s.waitStatus > 0) {
s = null; // traverse in case of concurrent cancellation
for (Node p = tail; p != h && p != null; p = p.prev) { 從后往前找
找到一個不是null的也不是被取消的
也就是找到隊列中第一個真正在等待的
if (p.waitStatus <= 0)
s = p;
}
}
不用排隊情況(人即線程)
情況一:根本沒有在等待像寒,前面可能有人,也可能沒有人
情況二:有在等待的瓜贾,但是等待的人是自己诺祸,前面肯定有人
要排隊的情況
有在真正等待的,并且第一個真正在等待的不是自己
s==null 就說明沒有等待的 **不用排隊** 隊列已經(jīng)初始化過了
s!=null就說明有等待的祭芦,
等待的人不是自己 **要排隊**
等待的人是自己 **不用排隊** 這里會比較困惑筷笨,因為這個情形的出現(xiàn)要結(jié)合后面的**acquireQueued方法來看**
因為如果你是第一個等待者,那么acquireQueued會再調(diào)用一次tryAcquire龟劲,而tryAcquire會再調(diào)用一次hasQueuedPredecessors胃夏,
那么這種情形就會出現(xiàn)了即等待的人是自己
if (s != null && s.thread != Thread.currentThread())
return true;
}
return false;
}
這個判斷排隊比較繞。昌跌。仰禀。
然后就是回到了之前的tryAcquire了,如果不需要排隊蚕愤,那么直接用CAS操作嘗試修改鎖的狀態(tài)答恶,如果修改成功那么獲取鎖就成功了。
如果不需要帶隊,但是獲取鎖失敗了那就說明實際上還是需要排隊的萍诱,因為這種情況就說明了存在多線程競爭
如果需要排隊就要調(diào)用addWaiter往隊列中添加等待者悬嗓,即排隊,這個方法內(nèi)部就不展開了砂沛,就是入隊烫扼,它用CAS操作保證了入隊順序是線程安全的。 注意排好隊后碍庵,線程還沒有阻塞映企。
排好隊后隊列中排隊的人看看自己能不能獲取鎖,如果不能就阻塞線程静浴,直到被喚醒
final boolean acquireQueued(final Node node, int arg) {
boolean interrupted = false;
try {
死循環(huán)堰氓,直到獲取鎖為止,當(dāng)然獲取失敗就會阻塞苹享,直到被喚醒双絮,然后再嘗試獲取 如此循環(huán)
for (;;) {
final Node p = node.predecessor();
情形一 加入隊列后如果自己是第一個等待的那么就在試著獲取一次鎖浴麻,
原因是再自己第一次嘗試獲取鎖失敗加入隊列后的這段時間,擁有鎖的那個線程可能釋放了鎖
更后面的等待者是沒有機會嘗試獲取鎖的
情形二阻塞后被喚醒
說明自己是第一個等待的 再嘗試獲取鎖
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
return interrupted;
}
決定是否阻塞 根據(jù)它前面的人的狀態(tài)
if (shouldParkAfterFailedAcquire(p, node))
interrupted |= parkAndCheckInterrupt(); 阻塞囤攀,并且阻塞醒來也是從這里開始
}
} catch (Throwable t) {
cancelAcquire(node);
if (interrupted)
selfInterrupt();
throw t;
}
}
shouldParkAfterFailedAcquire這個方法也是比較令人困惑的地方软免,也是我自己理解還不到位的地方
這個方法其實會執(zhí)行兩遍,第一遍是把前面等待者(如果有的話)的等待狀態(tài)設(shè)置好(設(shè)置成阻塞等待)
第二遍再確定自己是否需要阻塞
如果你都因為獲取鎖失敗
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
前面一個等待者的等待狀態(tài)
int ws = pred.waitStatus;
說明前面一個已經(jīng)在阻塞等待了焚挠,那么自然我也就要阻塞等待了
if (ws == Node.SIGNAL)
return true;
if (ws > 0) {
你前面的等待者(等待線程)可能被取消了 找到一個沒有被取消的
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
第一次執(zhí)行 把前面的等待者狀態(tài)設(shè)置好膏萧,因為它肯定已經(jīng)阻塞等待
pred.compareAndSetWaitStatus(ws, Node.SIGNAL);
}
return false;
}
這里說的比較亂。蝌衔。榛泛。
為什么要后面的人來設(shè)置前面的人的阻塞狀態(tài)呢,因為一個線程真正阻塞了噩斟,那么它的阻塞狀態(tài)才能設(shè)置曹锨,即先有阻塞,然后有阻塞狀態(tài)剃允,但是阻塞的線程就根本不能動了沛简,自然也不能設(shè)置阻塞狀態(tài)。
拿睡覺來打個比方硅急,你睡著還是沒睡著不能由你說了算覆享,你不可能告訴別人你睡著了,只能由別人來看营袜,只有別人才能看到你是否真正睡著了撒顿。
并且這段有解鎖過程相關(guān)性挺高的,以后有時間再說了荚板。
這篇文章主要是為了記錄自己在學(xué)習(xí)AQS時候的一些感受凤壁,也方便以后自己復(fù)習(xí)。所以用到的語言也不夠準(zhǔn)確跪另,過程不能保證正確拧抖。如果有同學(xué)看到這篇文章,一定要帶著懷疑的心態(tài)去看免绿。如果發(fā)現(xiàn)錯誤的地方希望能批評指出唧席。